Descrizione di base della rete

Il dataset è stato reperito al seguente link:

https://www.kaggle.com/datasets/trnguyen1510/the-marvel-comic-characters-partnerships

I dati sono una rete non pesata, contenenti 350 nodi e 346 archi, dove ogni nodo rappresenta un personaggio dell’universo Marvel e ogni arco rappresenta l’esistenza di una “interazione stretta” (partnership) tra due personaggi stabilita sui link presenti nelle pagine Wikipedia di ciascun personaggio, ricorrendo alla sezione “partnerships” presente in molte (ma non in tutte) le infobox riportate a lato della pagina.

Non era inizialmente chiaro se il grafo andasse considerato orientato o meno: il dataset è infatti fornito in due file csv separati, uno con i nodi e l’altro per gli archi, contenente la lista delle coppie di nodi indicati come source e target. Si nota tuttavia che, se trattato come grafo direzionato, la sua reciprocità vale 0: ciò si scontra con l’evidenza sperimentale di almeno 4 collaborazioni documentate invece come reciproche nelle corrispondenti pagine Wikipedia, ossia

Ad eccezione di Spider-Man & Spider-Man (Miles Morales) (essendo Miles Morales un personaggio piuttosto recente, apparso nel 2011), le altre connessioni sono da ritenere “storiche” (alcune risalenti alla golden age) ed è poco palusibile che non fossero già documentate correttamente nel 2018, anno di creazione del dataset. Pertanto, il dataset viene qui considerato come un grafo non direzionato affinché rifletta correttamente i legami di partnerships, per riuscire a dare un’intepretazione “sensata” ai risultati trovati sul grafo.

L’interpretazione non orientata del grafo trova conferma nel repository https://networks.skewed.de/net/marvel_partnerships#fnref:icon dove viene classificato come Undirected.

Il dataset contiene anche un attributo categoriale per ciascun nodo, dividendo i personaggi in eroi (0), cattivi (1) e personaggi “grigi” altrimenti detti antieroi (2); questi ultimi possiedono qualità sia degli eroi che dei cattivi, ossia personaggi solitamente privi di qualità eroiche che a volte compiono azioni moralmente corrette, ma agendo principalmente per interesse personale o in modi che sfidano i codici etici convenzionali.

Osservazione

Il dataset potrebbe essere ricondotto ad una situazione reale immaginando che i personaggi Marvel siano gli impiegati di un’azienda afferenti principalmente a due reparti distinti (eroi e cattivi), magri in competizione, più qualche altra figura con un ruolo meno strutturato (gli antieroi), e che si voglia studiare le interazioni strette tra i vari dipendenti.

Caricamento e preparazione dei dati

Il dataset scaricato è organizzato in due distinti file: nodes.csv e edges.csv:

  • in nodes.csv sono presenti la colonna group (la natura del personaggio), id (il nome del personaggio) e size (il numero di connessioni che ciascun personaggio ha con gli altri del dataset).
  • il file edges.csv contiene invece la colonna source e la colonna target indicanti il verso presunto della relazione tra i personaggi riportati nelle due colonne.

Si procede al loro caricamento in R e alla semplificazione di alcuni nomi, cercando laddove possibile di limitare la lunghezza di quelli più prolissi, in particolare rimuovendo le indicazioni tra parentesi come in 'Cyclone (Marvel Comics)').

rm(list=ls())
setwd("C:/Users/dario/Documents/Projects/Master/Networks/")

library(igraph)

myseed = 6174

# lettura file csv   
nodes_data = read.csv('nodes.csv') 
edges_data = read.csv('edges.csv')

# Semplificazione dei nomi lunghi
orig_char = c('Blackout (Lilin)', 
              'Blackout (Marcus Daniels)', 
              'Hawkeye (Kate Bishop)', 
              'Iron Man (Ultimate Marvel character)', 
              'Spider-Man (Miles Morales)', 
              'Spider-Woman (Gwen Stacy)', 
              'Spider-Woman (Jessica Drew)'
)
new_char = c('Blackout L.', 
             'Blackout M.D.', 
             'Hawkeye K.B.', 
             'Iron Man U.M.c.', 
             'Spider-Man M.M.', 
             'Spider-Woman G.S.', 
             'Spider-Woman J.D.'
)
for (n in 1:length(orig_char))
{
   nodes_data$id[nodes_data$id == orig_char[n]] = new_char[n]
   edges_data$source[edges_data$source == orig_char[n]] = new_char[n]
   edges_data$target[edges_data$target == orig_char[n]] = new_char[n]
}
# rimozione del contenuto tra parentesi
nodes_data$id = gsub("\\s*\\([^\\)]+\\)", "", nodes_data$id)
edges_data$source = gsub("\\s*\\([^\\)]+\\)", "", edges_data$source)
edges_data$target = gsub("\\s*\\([^\\)]+\\)", "", edges_data$target)

# converto i codici 0,1,2 in group in una versione testuale
group = nodes_data$group
nodes_data$group = NULL #rimuovo la colonna group, crea problemi a graph_from_data_frame
group_labels = c("hero", "villain", "antihero")
nodes_data$category = as.factor(group_labels[group + 1]) #reinserisco group come variabile categorica

# associo dei colori alle categorie
group_colors = c('darkgray', 'lightblue', 'lightsalmon')
group_colors_borders = c(c('gray50', 'lightblue3', 'lightsalmon3'))
# group_colors = c('lightblue', 'lightsalmon', 'darkgray') 
# group_colors_borders = c(c('lightblue3', 'lightsalmon3', 'gray50'))


node_colors = group_colors[as.numeric(nodes_data$category)]
border_colors = group_colors_borders[as.numeric(nodes_data$category)]

Creazione grafo non direzionato

marvel_Ugraph = graph_from_data_frame(d=edges_data, vertices=nodes_data, directed=F)

set.seed(myseed)
#my_layout = layout_with_fr#(marvel_Ugraph, start.temp = 100, niter=3000)
layout_full <- layout_with_fr(marvel_Ugraph)

conn = components(marvel_Ugraph)
id_maxconn = which(conn$csize== max(conn$csize))
nodes_maxconn = which(conn$membership == id_maxconn)
edges_maxconn <- E(marvel_Ugraph)[.from(nodes_maxconn) & .to(nodes_maxconn)]

V(marvel_Ugraph)$name = nodes_data$id
V(marvel_Ugraph)$category = nodes_data$category
V(marvel_Ugraph)$size = 3.5
V(marvel_Ugraph)$color = node_colors
V(marvel_Ugraph)$frame.color = border_colors
V(marvel_Ugraph)$label.cex = 0.3
V(marvel_Ugraph)$label.family = 'sans'
V(marvel_Ugraph)$label.color='black'

E(marvel_Ugraph)$width = 1#0.9
E(marvel_Ugraph)[edges_maxconn]$width= 1.2
E(marvel_Ugraph)$color = "darkolivegreen3" 
E(marvel_Ugraph)[edges_maxconn]$color = "darkolivegreen" 

par(mar = c(0, 0, 0, 0))
set.seed(myseed)
plot(marvel_Ugraph,
     layout=layout_full,
     vertex.color= 'cornsilk2', #'burlywood1',
     vertex.frame.color = 'cornsilk3', #'burlywood3',
     vertex.frame.width = 0.5,
     edge.width = 0.5,
)


set.seed(myseed)
plot(marvel_Ugraph,
     layout=layout_full,
     vertex.label = NA,
)
legend('topright', legend = levels(nodes_data$category), fill = group_colors, bty = 'n')
par(mar=c(5,4,4,2)+0.1)


marvel_Umaxconn = induced_subgraph(marvel_Ugraph, vids = nodes_maxconn, impl='create_from_scratch')

La rete presenta una grossa componente connessa, e poi altri sottografi connessi molto più piccoli, riconducibili probabilmente a personaggi minori o presenti in serie poco soggette a cross-over.

Analisi descrittiva a livello di rete

Densità

La densità \(\rho \in [0,1]\) può essere considerata una stima della probabilità di osservare un arco tra nodi selezionati casualmente. \[\begin{equation} \rho = \frac{\text{# di archi}}{\text{max # di archi}} \in [0,1] \end{equation}\]

Reciprocità

Nel caso di una rete direzionata l’indice di reciprocità \(R\) punta a quantificare quanto sia forte la tendenza a ricambiare una relazione: è definita come la frazione \(R\) di legami reciproci \[\begin{equation*} R = \frac{\text{# di archi reciproci}}{\text{# di archi}} \in [0,1] \end{equation*}\]

  • R = 0 implica che tutte le relazioni osservate tra i nodi nella rete non sono reciproche.
  • R = 1 implica che tutte le relazioni osservate tra i nodi nella rete sono reciproche (in una rete non direzionata, tale indice vale ovviamente sempre 1).

Transitività

Nell’ambito delle reti non direzionate, la transitivà indica la tendenza a formare legami a 3 tre tra i nodi, ed è misurabile attraverso il coefficiente di transitività o di clustering \(C\) \[\begin{equation*} C = \frac{\text{# di cammini $chiusi$ di lunghezza 2}}{\text{# di cammini di lunghezza 2}} \in [0,1] \end{equation*}\]

Assortatività

  • L’assortatività o omofilia modella la possibilità di osservare legami tra nodi che sono simili tra loro piuttosto che tra quelli che non lo sono.
  • La dissassortatività o eterofilia indica lo schema opposto

Limitando il concetto ad una rete non direzionata (non pesata) con \(m\) archi \(\{u_{ij}\} \in \{0,1\}\), e con attributi nodali nominali (eroi, cattivi e antieroi) \(c_i\), l’indice di assortatività \(r\) è definito attraverso la modularità \(Q\), che confronta il numero di archi all’interno delle comunità con quello atteso in una rete casuale \[\begin{align*} r &= \frac{Q}{Q_{\max}} \in [-1,1]\\ Q &= \frac{1}{2m} \sum_{ij} u_{ij} - \frac{k_i k_j}{2m} \delta(c_i, c_j)\\ \delta(c_i,c_j) &= \begin{cases} 1 & c_i=c_j \\ 0 & c_i \neq c_j\end{cases} \end{align*}\] essendo \(k_n\) il grado (cioè il # di archi incidenti) del nodo \(n\).

In R è disponible la funzione assortativity_nominal() che calcola \(r\) attraverso la frazione \(e_{ij}\) di archi che connettono i nodi di tipo \(c_i\) e tipo \(c_j\): \[\begin{align*} r &= \frac{\sum_i e_{ii} -\sum_i a_i b_i}{1-\sum_i a_i b_i}\\ a_i &= \sum_j e_{ij} \\ b_j &= \sum_i e_{ij} \end{align*}\]


# grafo NON DIREZIONATO
rho_u = edge_density( marvel_Ugraph)
R_u = reciprocity(  marvel_Ugraph)
C_u = transitivity( marvel_Ugraph)
r_u = assortativity_nominal(marvel_Ugraph, V(marvel_Ugraph)$category)

rho_c = edge_density( marvel_Umaxconn)
R_c = reciprocity(  marvel_Umaxconn)
C_c = transitivity( marvel_Umaxconn)
r_c = assortativity_nominal(marvel_Umaxconn, V(marvel_Umaxconn)$category)

table_graph <- data.frame(
   "Whole" = c(rho_u, R_u, C_u, r_u),
   "Maxconn" = c(rho_c, R_c, C_c, r_c),
   row.names = c("Densità", "Reciprocità", "Transitività", "Assortatività")
)
table_graph$Whole = sprintf("%.3f", table_graph$Whole)
table_graph$Maxconn = sprintf("%.3f", table_graph$Maxconn)


(table_graph)

Osservazioni

  • la densità aumenta nel caso del sottografo massimamente connesso dato il minor numero di archi “mancanti” rispetto al grafo completo con tutte le altre componenti
  • la bassa transitività in entrambi i casi riflette la natura dei legami definiti dal concetto di “partnerships”, trattandosi cioè di associazioni strette (tipo Iron-Man e War-Machine, che operano spesso in coppia) diverse dalle collaborazioni in team (tipo Avengers)
  • l’assortatività positiva (non trascurabile, in quanto superiore a 0.6 sia nel grafo completo che per il sottografo maggiormente connesso) riflette la natura dell’attributo, ed è indice che i cattivi si legano solitamente ad altri cattivi e i buoni ad altri buoni.

Analisi descrittiva a livello di nodi

Ci concentriamo qui sulla componente maggiormente connessa marvel_Umaxconn: del grafo non direzionato marvel_Ugraph con archi \(u_{ij} \in \{0,1\}\): questo sia perché gli altri sottografi sono relativi a personaggi o serie molto meno rilevanti nell’universo Marvel, sia perché si vuole confrontare in modo “equo” i tre diversi concetti di centralità di seguito definiti.

set.seed(myseed)
indices_maxconn_in_ugraph = match(V(marvel_Umaxconn)$name, V(marvel_Ugraph)$name)
layout_maxconn = layout_full[indices_maxconn_in_ugraph, ]
set.seed(myseed)
par(mar = c(0, 0, 0, 0))
set.seed(myseed)
plot(
   marvel_Umaxconn,
   layout = layout_maxconn,
   edge.width = 0.5
)
legend('topright', legend = levels(nodes_data$category), fill = group_colors, bty = 'n')
par(mar=c(5,4,4,2)+0.1)

Degree Centrality (centralità di grado)

Il grado (degree) \(\zeta_i^d\) o la centralità di grado di un nodo \(i\) indica il numero di legami che coinvolgono \(i\) \[\begin{equation*} \zeta_i^d = \sum_{ij} u_{ij} \end{equation*}\] Per una rete con \(n\) nodi, \(\zeta_i^d \in [0,n-1]\)

La sua versione normalizzata \(\tilde\zeta_i^d\) è quindi definita come \[\begin{equation*} \tilde\zeta_i^d = \frac{\sum_{ij} u_{ij}}{n-1} \in [0,1] \end{equation*}\]

La centralità di grado fornisce informazioni sull’influenza diretta del nodo nella rete e sul suo accesso alle informazioni di prima mano

plot_centrality <- function(centr_norm, thr_q, scaleText=F)
{
   thr_norm = quantile(centr_norm, thr_q)
   print(thr_norm)
   nameoff = which(centr_norm<thr_norm)
   showname = V(marvel_Umaxconn)$name
   showname[nameoff] = NA
   
   cexText = 0.5
   if (scaleText==T)
      cexText = centr_norm*12
      
   par(mar=c(0,0,0,0))
   plot(marvel_Umaxconn,
        layout=layout_maxconn,
        vertex.size = centr_norm*22,
        vertex.label = showname,
        vertex.label.cex = cexText,#0.5,
        edge.width = 0.4
   )
   par(mar=c(5,4,4,2)+0.1)   
}

deg = degree(marvel_Umaxconn)
deg_norm = degree(marvel_Umaxconn, normalized = T)

par(mar=c(4,4,3,0))
hist(deg_norm, freq=F, main='degree centrality', col='palegreen3', border='palegreen4')
grid(lty='solid', lwd=0.5, col='white', nx=NA, ny=NULL)
par(mar=c(5,4,4,2)+0.1)


thr_q = 0.95
plot_centrality(deg_norm, thr_q, T)
       95% 
0.03333333 

ord = order(deg_norm, decreasing = T)
degrank = data.frame(name = V(marvel_Umaxconn)$name[ord], degree = deg[ord], degnorm=deg_norm[ord])
(degrank)

Closeness centrality (centralità di prossimità)

Per valutare la prossimità di un nodo rispetto agli altri nodi occorre prima introdurre introdurre la farness (lontananza) \(l_i\) di un nodo \[\begin{equation*} l_i = \sum d_{ij} \end{equation*}\] essendo \(d_{ij}\) la lunghezza del cammino più breve tra il nodo \(i\) ed il nodo \(j\).

La closeness \(\zeta_i^c\) di un nodo \(i\) vale \[\begin{equation*} \zeta_i = \frac{1}{l_i} \end{equation*}\]

Poiché il massimo di \(\zeta_i^c\) si verifica quando \(i\) è connesso a tutti quanti gli altri nodi della rete (configurazione a stella), cioè \(\zeta_{\max}^c = 1/\sum_{i \neq j} 1 = 1/(n-1)\), la closeness normalizzata \(\tilde\zeta_i^c\) vale \[\begin{equation*} \tilde\zeta_i^c = \frac{\zeta_i^c}{\zeta_{\max}^c} = \frac{n-1}{l_i} \in [0,1] \end{equation*}\]

La closeness è utilizzata per individuare nodi strategici per la trasmissione di informazioni: indica se un nodo ha un accesso rapido e diretto agli altri nodi

Osservazione

Poiché in marvel_Ugraph non esiste sempre almeno un cammino tra tutte le coppie di nodi), applichiamo il concetto di closeness centrality solo al sottografo connesso più numeroso.

clo = closeness(marvel_Umaxconn)
clo_norm = closeness(marvel_Umaxconn, normalized = T)

par(mar=c(4,4,3,0))
hist(clo_norm, freq=F, main='closeness centrality', col='palegreen3', border='palegreen4')
grid(lty='solid', lwd=0.5, col='white', nx=NA, ny=NULL)
par(mar=c(5,4,4,2)+0.1)


plot_centrality(clo_norm, thr_q)
      95% 
0.1727447 

ord = order(clo_norm, decreasing = T)
clorank = data.frame(name = V(marvel_Umaxconn)$name[ord], closeness = clo[ord], clonorm=clo_norm[ord])
(clorank)

Betweenness centrality (centralità di intermediazione)

L’idea alla base della betweenness centrality è che un nodo sia maggiormente centrale per una data rete quanto più si trova tra molti altri nodi.

La betweenness \(\zeta_i^b\) di un nodo \(i\) è la frazione dei cammini minimi che passano attraverso \(i\) \[\begin{align*} \zeta_i^b &= \sum_{j>1} \sum_{k>j} \frac{n_{jk}^i}{g_{jk}} \qquad \textsf{con}\\ \frac{n_{jk}^i}{g_{jk}} &= 0 \quad\textsf{se}\quad n_{jk}^i = g_{jk} = 0 \\ n_{jk}^i &= \textsf{# di cammini minimi tra i nodi $j$ e $k$ passanti attraverso $i$}\\ g_{jk} &= \textsf{# di cammini minimi tra i nodi $j$ e $k$ in totale} \end{align*}\]

Poiché il massimo di \(\zeta_i^b\) si verifica quando \(i\) si trova su tutti i percorsi geodetici che collegano ogni coppia di altri nod, ossia si ha \[\begin{equation} n_{jk}^i=g_{jk}\enspace \forall\, (j,k) \quad \Rightarrow \quad \zeta_{\max}^b = \sum_{j>i} \sum_{k>i} 1 = \frac{(n-1)(n-2)}{2} \end{equation}\] il valore normalizzato \(\hat\zeta_i^b\) è definito come \[\begin{equation} \tilde\zeta_i^b = \frac{\zeta_i^b}{(n-1)(n-2)} \end{equation}\]

La betweenness centrality aiuta ad identificare nodi strategici o vulnerabili in una rete: indica quanto un vertice sia cruciale come “snodo” per i flussi di informazioni o connessioni nel grafo.

Osservazione

Coinvolgendo di nuovo i cammini minimi, anche in questo caso, prendiamo qui in considerazione solo il sottografo connesso più numeroso.

bet = betweenness(marvel_Umaxconn)
bet_norm = betweenness(marvel_Umaxconn, normalized = T)
par(mar=c(4,4,3,0))
hist(bet_norm, freq=F, main='betweenness centrality', col='palegreen3', border='palegreen4')
grid(lty='solid', lwd=0.5, col='white', nx=NA, ny=NULL)
par(mar=c(5,4,4,2)+0.1)

plot_centrality(bet_norm, thr_q)
     95% 
0.222843 

ord = order(bet_norm, decreasing = T)
betrank = data.frame(name = V(marvel_Umaxconn)$name[ord], betweenness = clo[ord], betnorm=bet_norm[ord])
(betrank)

Nodi importanti

topnodes <- function(centr_norm, thr_q)
{
   thr_norm = quantile(centr_norm, thr_q)
   idon = which(centr_norm>=thr_norm)
   return(idon)
}

hc = quantile(clo_norm, thr_q)
hb = quantile(bet_norm, thr_q)

idtop_deg = topnodes(deg_norm, thr_q)
idtop_clo = topnodes(clo_norm, thr_q)
idtop_bet = topnodes(bet_norm, thr_q)

idtop = union(union(idtop_deg, idtop_clo), idtop_bet)
idxor = setdiff(union(idtop_deg, union(idtop_clo, idtop_bet)), intersect(idtop_deg, intersect(idtop_clo, idtop_bet)))
idbest = intersect(intersect(idtop_deg, idtop_clo), idtop_bet)

par(mar = c(4, 4, 2, 1))
scaldeg = 6
scores = data.frame(name =V(marvel_Umaxconn)$name, deg=deg, clo=clo_norm, bet=bet_norm)
plot(scores$bet, scores$clo, 
     ylab = 'Closeness', 
     xlab = 'Betweenness',
     main = 'Node centralities',
     xlim = c(0,0.9),
     cex = scores$deg/scaldeg, 
     pch = 21, 
     col = V(marvel_Umaxconn)$color,
     bg = "white",
     bty='n'
)
points(scores$bet[idtop_deg], scores$clo[idtop_deg], cex=scores$deg[idtop_deg]/scaldeg, 
       pch = 21, col=V(marvel_Umaxconn)$color[idtop_deg], bg=V(marvel_Umaxconn)$color[idtop_deg])
abline(h = hc, lty = 2)
abline(v = hb, lty = 2)
grid()
text(y = hc, x = 0.86, '0.95 quantile', pos = 1, cex=0.7)
text(x = hb, y = 0.215, '0.95 quantile', pos = 4, cex=0.7)
text(cex=0.5, scores$bet[idxor], scores$clo[idxor], labels=scores$name[idxor], pos=4)
text(cex=0.6, scores$bet[idbest], scores$clo[idbest], labels=scores$name[idbest], pos=4, font=2)

mindeg = min(scores$deg)
meddeg = 6
maxdeg = max(scores$deg)
legend_deg = c(mindeg/scaldeg, meddeg/scaldeg, maxdeg/scaldeg) 
legend_degtext = c('1 (min)', '6 (0.95q.)', '12 (max)')

legend('topright', cex = 0.7, bty='n',
       legend = legend_degtext,
       pt.cex = legend_deg,
       pch=21, title='degree scale')
legend('topleft', cex = 0.7, bty='n',
       legend = c('antiheroes', 'heroes', 'villains'),
       pt.cex = 1,
       pch=15,
       col = group_colors) #title = 'category:'
legend('bottomright', cex=0.7, pch=15, pt.cex=1, col='black', bty='n',
       legend = 'Filled points: degree > 0.95 quantile')
#legend='degree over 0.95 quantile as filled points')

par(mar = c(5, 4, 4, 2) + 0.1)

Centralizzazione di un grafo

Passiamo adesso agli indici di centralizzazione della rete: essi vanno a misurare il grado di eterogeneità dei nodi rispetto alla centralità analizzata (degree, closeness e betweenness): \[\begin{equation*} CI = \frac{\sum_i (C_{\max}-C_i)}{\max_Y \sum_i (C_{\max}-C_i)} \in [0,1] \end{equation*}\]

  • \(C_i\) è il valore di centralità su ciascun nodo \(i\), e \(C_{\max}=\max_i C_i\),
  • con \(\max_Y\) riferito su tutte le possibili configurazioni (\(\Rightarrow\) conf. a stella)

Ne segue che

  • Per \(CI=0\), tutti i nodi sono ugualmente centrali (conf. a cerchio)
  • Per \(CI=1\), c’è un solo nodo centrale e gli altri minimalmente centrali (conf. a stella)
CIdeg = centr_degree(marvel_Umaxconn, loops = F)$centralization
CIclo = centr_clo(marvel_Umaxconn)$centralization
CIbet = centr_betw(marvel_Umaxconn, directed = F)$centralization

(c(CIdeg, CIclo, CIbet))
[1] 0.05350714 0.16570278 0.62927840
#par(mar=c(5,4,4,2)+0.1) #mfrow = c(1,1),  
par(mfrow = c(1,3))
plot_centrality(deg_norm, thr_q, T); title(paste('deg. centr. ', round(CIdeg,3)))
       95% 
0.03333333 
plot_centrality(clo_norm, thr_q);    title(paste('clo. centr. ', round(CIclo,3)))
      95% 
0.1727447 
plot_centrality(bet_norm, thr_q);    title(paste('bet. centr. ', round(CIbet,3)))
     95% 
0.222843 
par(mfrow = c(1,1))

Considerazioni

  • L’indice di centralizzazione sulla degree centrality misura quanto le connessioni siano concentrate in pochi nodi, ed il suo basso valore indica che le connessioni sono distribuite in modo uniforme tra i nodi, ossia
    • la maggior parte dei nodi nella rete ha un numero simile di connessioni dirette (sebbene alcuni nodi come Spider-Man e Capitan America abbiano più connessioni di altri, la differenza non è così marcata)
    • non ci sono pochi nodi che dominano la rete con un numero eccessivo di connessioni (tipo conf. a cerchio).
  • L’indice di centralizzazione sulla closeness centrality indica quanto la “vicinanza” sia concentrata in pochi nodi (indice elevato) oppure le distanze medie tra i nodi sono più uniformi (indice basso); il valore medio-basso indica che alcuni nodi sono più centrali e possono raggiungere gli altri più facilmente, ma la maggior parte non è eccessivamente lontana
  • L’indice di centralizzazione sulla betweenness centrality indica quanto il controllo sui percorsi più brevi sia concentrato in pochi nodi, ed il suo valore elevato significa che pochi “nodi ponte” controllano un gran numero di percorsi più brevi, fungendo da “colli di bottiglia” nella rete (quindi la rimozione di un nodo con un’alta betweenness centrality può interrompere significativamente la comunicazione e l’interazione tra diverse parti della rete)

La centralità superiore di Spider-Man, Venom e Red Skull in tutte le varianti considerate suggerisce che le loro interazioni e relazioni siano cruciali per lo sviluppo delle trame.

Inoltre, considerando che due di loro sono “cattivi”, si capisce come le loro trame siano fondamentali all’interno della rete, in quanto elementi che rompono gli equilibri.

Stochastic Block Model

Presupponendo un modello a blocchi latenti \(B_1 \dots B_Q\), sia \(\boldsymbol{\vartheta} = [\alpha_1 \dots \alpha_Q, \pi_{11} \dots \pi_{QQ}]^\top\) il vettore di parametri associato al modello, dove:

Si vuole stimare i parametri \(\boldsymbol{\vartheta}\) a massima verosimiglianza data una realizzazione \(\mathtt{U}\) (il grafo in esame) per la matrice di adiacenza \(\mathbf{Y}\): \[\begin{equation*} \hat{\boldsymbol{\vartheta}} = \arg \max_\vartheta \log \mathsf{P}(\mathbf{Y}=\mathtt{U}) = \arg \max_\vartheta \log \sum_{\mathtt{Z}} \mathsf{P}(\mathbf{Y}=\mathtt{Y}|\mathbf{Z}=\mathtt{Z}; \boldsymbol{\vartheta}) \mathsf{P}(\mathbf{Z}=\mathtt{Z}; \boldsymbol{\vartheta}) \end{equation*}\] dove \(\mathbf{Z} = \{z_{iq}\}\) è la matrice aleatoria di tutte le variabili latenti nel modello \(z_{iq} = \boldsymbol{1}(\text{nodo $i \in$ blocco $B_q$})\).

La determinazione di \(\hat{\boldsymbol{\vartheta}}\) avviene tramite un algoritmo di Expectation-Maximization Variazionale che minizza un lower bound della log-likelihood attraverso una formulazione trattabile del problema.

SBS su marvel_Umaxconn

A causa della sparsità del grafo in questione, SBM fa molta fatica a trovare dei blocchi latenti:

  • anche applicando SBM al sottografo marvel_Umaxconn (la componente maggiormente connessa), si hanno dei risultati poco soddisfacenti, come si può vedere di seguito;
  • nel caso del grafo completo marvel_Ugraph addirittura viene restituito un solo unico blocco.
library(sbm)
library(lattice)
library(cowplot)

Yconn = as_adjacency_matrix(marvel_Umaxconn, sparse=F)
sbmConn = estimateSimpleSBM(Yconn, 'bernoulli', estimOptions=list(verbosity=0))
plot1 <- ggdraw() + draw_plot({ plotMyMatrix(Yconn)})
plot2 <- ggdraw() + draw_plot({ plot(sbmConn,type="data")})
plot3 <- ggdraw() + draw_plot({ plot(sbmConn,"expected")})

# Combina i grafici in una griglia 1x3
plot_grid(plot1, plot2, plot3, nrow = 1)

SBM su marvel_Umaxconn esteso

Estensione degli archi nel grafo

Per poter valutare lo SBM in un contesto più ricco di connessioni tra i nodi, marvel_Umaxconn è stato esteso andando a recuperare per ogni personaggio relativo ai suoi 181 nodi in quale fumetto avesse fatto la sua comparsa; ad esempio

Personaggio 1° Fumetto
Doctor Doom Fantastic Four
Hulk The Incredible Hulk
Iron Man Tales of Suspense
Loki Journey into Mystery
Magneto The Uncanny X-Men
Thing Fantastic Four
Thor Journey into Mystery

Queste informazioni poi sono state impiegate per creare dei nuovi archi per ogni coppia di personaggi apparsi nella stessa collana, a testimonianza di un probabile legame di interazione debole.

library(readxl)
marvel_comics <- read_excel("marvel_comics.xlsx")
pid <- marvel_comics$id
for (n in 1:length(orig_char))
{
   pid[pid == orig_char[n]] = new_char[n]
}
pid = gsub("\\s*\\([^\\)]+\\)", "", pid)

seq_pid = match(V(marvel_Umaxconn)$name, pid)
pid = pid[seq_pid]
comics = as.factor(marvel_comics$comics[seq_pid])

score_comics <- table(comics)
comics_rank <- sort(score_comics, decreasing=T)
par(mar=c(9,4,2,0))
barplot(comics_rank,
        main = "Comics character ranking",
        xlab = "",
        ylab = "# characters",
        col='palegreen3', border='palegreen4',
        las = 2, # Etichette dell'asse x verticali
        cex.names = 0.5 
)
grid(lty='solid', lwd=0.5, col='white', nx=NA, ny=NULL)
par(mar=c(5,4,4,2)+0.1)

# Estensione del grafo marvel_Umaxconn
V(marvel_Umaxconn)$comics = comics
Yext = 2*as_adjacency_matrix(marvel_Umaxconn, sparse=F)
for (i in 1:length(comics))
{
   pi = pid[i]
   for (j in 1:length(comics)) 
   {
      pj = pid[j]
      if (comics[i] == comics[j] && pi != pj) 
      {
         #Yext[pi, pj] =  1
         #Yext[pj, pi] =  1
         
         if (Yext[pi, pj]==0)
         {
            Yext[pi, pj] = 1
            Yext[pj, pi] = 1
         }
      }
   }
}
marvel_Uext <- graph_from_adjacency_matrix(Yext, mode = 'undirected', weighted=T)
V(marvel_Uext)$comics = comics
V(marvel_Uext)$size = 3.5
V(marvel_Uext)$label.cex = 0.3
V(marvel_Uext)$label.family = 'sans'
V(marvel_Uext)$label.color='black'
E(marvel_Uext)$width = 0.6
E(marvel_Uext)$color = "gray"


color_comics <- rep("darkgray", length(comics_rank))
color_comics[1:6] = c('lightskyblue', 'sandybrown', 'yellowgreen', 'pink', 'gold3', 'plum3')
color_nodeByComics = rep("darkgray", vcount(marvel_Uext))
for (i in 1:vcount(marvel_Uext)) {
  com = V(marvel_Uext)$comics[i]
    id_com = which(names(comics_rank) == com)
    color_nodeByComics[i] = color_comics[id_com]
}
col_leg = c(color_comics[1:6], 'darkgray')
lab_leg = c(names(comics_rank)[1:6], 'others')

par(mar = c(0, 0, 0, 0)) #mfrow = c(1,2), 
set.seed(myseed)
plot(marvel_Umaxconn,
     layout=layout_maxconn,
     vertex.frame.width = 0.5,
     vertex.color= color_nodeByComics,
     edge.width = 0.1,
)
legend("topright", legend=lab_leg, fill=col_leg, title='Comics', bty='n', cex=0.6)
par(mar=c(5,4,4,2)+0.1)

color_edge = rep("lightgray", length(E(marvel_Uext)$weight))
idstrong = which(E(marvel_Uext)$weight>1)
color_edge[idstrong] = 'black'

par(mar = c(0, 0, 0, 0))

set.seed(myseed)
plot(marvel_Uext,
     layout=layout_maxconn,
     vertex.frame.width = 0.5,
     vertex.color= color_nodeByComics,#V(marvel_Uext)$comics,
     edge.width = 0.1*E(marvel_Uext)$weight,
     edge.color = color_edge#E(marvel_Uext)$weight,
)
legend("topright", legend=lab_leg, fill=col_leg, title='Comics', bty='n', cex=0.6)
par(mar=c(5,4,4,2)+0.1) #mfrow = c(1,1),  

Applicazione dello SBM

Versione binarizzata (Bernoulli)

Selezione del # Q di blocchi latenti tramite ICL
Selezione del # Q di blocchi latenti tramite ICL
library(colorspace)

Yext_binary <- ifelse(Yext > 0.5, 1, 0)
sbm = estimateSimpleSBM(Yext_binary, 'bernoulli', estimOptions=list(verbosity=0))
plot1 <- ggdraw() + draw_plot({ plotMyMatrix(Yext_binary) })
plot2 <- ggdraw() + draw_plot({ plot(sbm, type = "data") })
plot3 <- ggdraw() + draw_plot({ plot(sbm, "expected") })

plot_grid(plot1, plot2, plot3, nrow = 1)

Nella figura superiore, da sinistra verso destra: la matrice di adiacenza binaria in ingresso, la matrice organizzata in blocchi e la sua versione in termini di probabilità (che rivedremo anche dopo).

(sbm$blockProp)
[1] 0.15448025 0.07751530 0.06652031 0.05552532 0.05552532 0.05552532 0.03903283
[8] 0.08301217 0.41286318
par(mar=c(4,4,3,0))
barplot(sbm$blockProp,
        main = "sbm$blockProp",
        xlab = "blocks",
        ylab = "prob.",
        col='palegreen3', border='palegreen4',
        # las = 2, 
        cex.names = 1,
        names.arg = 1:length(sbm$blockProp)
)
grid(lty='solid', lwd=0.5, col='white', nx=NA, ny=NULL)
par(mar=c(5,4,4,2)+0.1)

Sopra, le probabilità a priori di appartenza dei vari nodi ai diversi blocchi.

# connectivity parameters
(round(sbm$connectParam$mean,3))
       [,1]  [,2]  [,3]  [,4]  [,5]  [,6]  [,7]  [,8]  [,9]
 [1,] 0.994 0.001 0.007 0.005 0.002 0.002 0.012 0.001 0.015
 [2,] 0.001 0.986 0.001 0.001 0.001 0.001 0.011 0.001 0.013
 [3,] 0.007 0.001 0.983 0.018 0.009 0.009 0.036 0.055 0.004
 [4,] 0.005 0.001 0.018 0.980 0.001 0.031 0.029 0.001 0.021
 [5,] 0.002 0.001 0.009 0.001 0.979 0.021 0.001 0.053 0.000
 [6,] 0.002 0.001 0.009 0.031 0.021 0.979 0.015 0.001 0.003
 [7,] 0.012 0.011 0.036 0.029 0.001 0.015 0.969 0.001 0.008
 [8,] 0.001 0.001 0.055 0.001 0.053 0.001 0.001 0.216 0.000
 [9,] 0.015 0.013 0.004 0.021 0.000 0.003 0.008 0.000 0.039
levelplot(sbm$connectParam$mean,
          col.regions = colorRampPalette(c("yellow", "purple"))(100), 
          main = 'sbm$connectParam$mean',
          xlab = "Colonne", ylab = "Righe")

Sopra, le probabilità di connessione intra- e inter- blocco (con le righe ordinate dal basso verso l’alto); in questo caso, sebbene lo SBM non cerchi di proposito le comunità, sono stati ritrovati 8 cluster con alta probabilità interna, mentre i collegamenti tra blocchi diversi sono molto poco probabili.

set.seed(myseed)
par(mar=c(0,0,0,0))
plot(sbm, type = "meso")
par(mar=c(5,4,4,2)+0.1)

Sopra, la rappresentazione in forma mesoscopica della matrice precedente:

  • ogni nodo indica un blocco, di dimensioni proporzionali al # di nodi allocati in ognuno di essi
  • lo spessore degli archi è proporzionale alle probabilità di creare archi tra nodi di blocchi diversi (o dello stesso blocco, per gli archi self loop)

Assegnamento dei nodi per blocco

color_blocks = rep("darkgray", length(sbm$memberships))
color_blocks[1:8] = c('dodgerblue2', 'orange', 'limegreen', 'pink', 'gold4', 'mediumorchid', 'gold', 'firebrick')

block_rank = sort(sbm$memberships, decreasing=T)
color_nodeByBlocks = rep("darkgray", vcount(marvel_Uext))
for (i in 1:vcount(marvel_Uext)) 
{
   id_com = sbm$memberships[i]
   color_nodeByBlocks[i] = color_blocks[id_com]
}
col_leg = c(color_blocks[1:9])
lab_leg = c(1:9)
#lab_leg <- c(names(color_blocks)[1:8], '>8')

par(mar=c(0,0,0,0))
plot(marvel_Uext,
     layout=layout_maxconn,
     vertex.frame.width = 0.5,
     vertex.color= color_nodeByBlocks,#sbm$memberships,
     edge.width = 0.1,
)
legend("topright", legend=lab_leg, fill=col_leg, title='Blocks', bty='n', cex=0.6)
par(mar=c(5,4,4,2)+0.1)

Confronto tra comics e blocchi
Confronto tra comics e blocchi

Di fatto SBM ha ritrovato un raggruppamento in base al fumetto di prima comparsa:

  • dal confronto visivo si nota che i primi 3 blocchi corrispondono ai primi tre fumetti, e anche per il sesto;
  • il blocco 5 dovrebbe corrispondere al fumetto #4 e viceversa (i fumetti #3,4,5 avevano la stessa numerosità di nodi)
  • il 7 blocco corrisponde al fumetto #7 per numerosità (Tales to Astonish, non indicato come codice colore)
  • per il gruppo 8, si veda la seguente tabella
id7 = which(sbm$memberships==7)
tab7 = data.frame(Personaggio = V(marvel_Uext)$name[id7], Fumetto = V(marvel_Uext)$comics[id7])
(tab7)

id8 = which(sbm$memberships==8)
tab8 = data.frame(Personaggio = V(marvel_Uext)$name[id8], Fumetto = V(marvel_Uext)$comics[id8])
(tab8)

Probabilità a posteriori di assegnazione di un nodo ai vari blocchi

levelplot(t(sbm$probMemberships),
          col.regions = colorRampPalette(c("yellow", "purple"))(100), 
          main = 'sbm$probMemberships',
          xlab = "blocchi", ylab = "nodi")

Versione pesata (Multinomiale???)

# sbm = estimateSimpleSBM(Yext, 'poisson', estimOptions=list(verbosity=0))
# 
# plot1 <- ggdraw() + draw_plot({ plotMyMatrix(Yext) })
# plot2 <- ggdraw() + draw_plot({ plot(sbm, type = "data") })
# plot3 <- ggdraw() + draw_plot({ plot(sbm, "expected") })
# 
# plot_grid(plot1, plot2, plot3, nrow = 1)
# 
# block_rank = sort(sbm$memberships, decreasing=T)
# color_nodeByBlocks = rep("darkgray", vcount(marvel_Uext))
# for (i in 1:vcount(marvel_Uext)) 
# {
#    blk = sbm$memberships[i]
#    id_com <- which(block_rank == blk)
#    color_nodeByBlocks[i] = color_blocks[id_com[1]]
# }
# col_leg = c(color_blocks[1:8], 'darkgray')
# lab_leg <- c(names(color_blocks)[1:8], '>8')
# 
# par(mar=c(0,0,0,0))
# plot(marvel_Uext,
#      layout=layout_maxconn,
#      vertex.frame.width = 0.5,
#      vertex.color= sbm$memberships,
#      edge.width = 0.1,
#      edge.color = color_edge,
# )
# par(mar=c(5,4,4,2)+0.1)
# 
# (sbm$blockProp)
# par(mar=c(4,4,3,0))
# barplot(sbm$blockProp,
#         main = "sbm$blockProp",
#         xlab = "blocks",
#         ylab = "prob.",
#         col='palegreen3', border='palegreen4',
#         # las = 2, 
#         cex.names = 1,
#         names.arg = 1:length(sbm$blockProp)
# )
# grid(lty='solid', lwd=0.5, col='white', nx=NA, ny=NULL)
# par(mar=c(5,4,4,2)+0.1)
# 
# # connectivity parameters
# (round(sbm$connectParam$mean,3))
# levelplot(sbm$connectParam$mean,
#           col.regions = colorRampPalette(c("yellow", "purple"))(100), 
#           main = 'sbm$connectParam$mean',
#           xlab = "Colonne", ylab = "Righe")
# 
# set.seed(myseed)
# par(mar=c(0,0,0,0))
# plot(sbm, type = "meso")
# par(mar=c(5,4,4,2)+0.1)
LS0tDQp0aXRsZTogIkVsYWJvcmF0byBwZXIgTmV0d29yayBEYXRhIEFuYWx5c2lzOiBpbCBkYXRhc2V0IE1hcnZlbCBDb21pYyBDaGFyYWN0ZXJzIFBhcnRuZXJzaGlwcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgRGVzY3JpemlvbmUgZGkgYmFzZSBkZWxsYSByZXRlIA0KDQpJbCBkYXRhc2V0IMOoIHN0YXRvIHJlcGVyaXRvIGFsIHNlZ3VlbnRlIGxpbms6IA0KDQpodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3Rybmd1eWVuMTUxMC90aGUtbWFydmVsLWNvbWljLWNoYXJhY3RlcnMtcGFydG5lcnNoaXBzDQoNCkkgZGF0aSBzb25vIHVuYSAqKnJldGUgbm9uIHBlc2F0YSoqLCBjb250ZW5lbnRpICoqMzUwIG5vZGkgZSAzNDYgYXJjaGkqKiwgZG92ZSBvZ25pICoqbm9kbyoqIHJhcHByZXNlbnRhIHVuICoqcGVyc29uYWdnaW8qKiBkZWxsJ3VuaXZlcnNvIE1hcnZlbCBlIG9nbmkgKiphcmNvKiogcmFwcHJlc2VudGEgbCdlc2lzdGVuemEgZGkgdW5hICoqImludGVyYXppb25lIHN0cmV0dGEiKiogKCpwYXJ0bmVyc2hpcCopIHRyYSBkdWUgcGVyc29uYWdnaSAqc3RhYmlsaXRhIHN1aSBsaW5rIHByZXNlbnRpIG5lbGxlIHBhZ2luZSBXaWtpcGVkaWEgZGkgY2lhc2N1biBwZXJzb25hZ2dpbyosIHJpY29ycmVuZG8gYWxsYSBzZXppb25lICJwYXJ0bmVyc2hpcHMiIHByZXNlbnRlIGluIG1vbHRlIChtYSBub24gaW4gdHV0dGUpIGxlICoqaW5mb2JveCoqIHJpcG9ydGF0ZSBhIGxhdG8gZGVsbGEgcGFnaW5hLiANCg0KTm9uIGVyYSBpbml6aWFsbWVudGUgY2hpYXJvIHNlIGlsIGdyYWZvIGFuZGFzc2UgY29uc2lkZXJhdG8gb3JpZW50YXRvIG8gbWVubzogaWwgZGF0YXNldCDDqCBpbmZhdHRpIGZvcm5pdG8gaW4gZHVlIGZpbGUgY3N2IHNlcGFyYXRpLCB1bm8gY29uIGkgbm9kaSBlIGwnYWx0cm8gcGVyIGdsaSBhcmNoaSwgY29udGVuZW50ZSBsYSBsaXN0YSBkZWxsZSBjb3BwaWUgZGkgbm9kaSBpbmRpY2F0aSBjb21lIGBzb3VyY2VgIGUgYHRhcmdldGAuDQpTaSBub3RhIHR1dHRhdmlhIGNoZSwgKipzZSB0cmF0dGF0byBjb21lIGdyYWZvIGRpcmV6aW9uYXRvLCBsYSBzdWEgcmVjaXByb2NpdMOgIHZhbGUgMDoqKiBjacOyIHNpIHNjb250cmEgY29uIGwnZXZpZGVuemEgc3BlcmltZW50YWxlIGRpIGFsbWVubyA0IGNvbGxhYm9yYXppb25pIGRvY3VtZW50YXRlIGludmVjZSBjb21lIHJlY2lwcm9jaGUgbmVsbGUgY29ycmlzcG9uZGVudGkgcGFnaW5lIFdpa2lwZWRpYSwgb3NzaWENCg0KKiBgU3BpZGVyLU1hbmAgKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NwaWRlci1NYW4pICYgYEJsYWNrIENhdGAgKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0JsYWNrX0NhdF8oTWFydmVsX0NvbWljcykpDQoqIGBTcGlkZXItTWFuYCAoaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU3BpZGVyLU1hbikgJiBgU3BpZGVyLU1hbiAoTWlsZXMgTW9yYWxlcylgIChodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9NaWxlc19Nb3JhbGVzKQ0KKiBgQ2FwdGFpbiBBbWVyaWNhYCAoaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ2FwdGFpbl9BbWVyaWNhKSAmIGBCdWNreSBCYXJuZXNgIChodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9CdWNreV9CYXJuZXMpDQoqIGBJcm9uIE1hbmAgKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0lyb25fTWFuKSAmIGBXYXIgTWFjaGluZWAgKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1dhcl9NYWNoaW5lKQ0KDQpBZCBlY2NlemlvbmUgZGkgYFNwaWRlci1NYW5gICYgYFNwaWRlci1NYW4gKE1pbGVzIE1vcmFsZXMpYCAoZXNzZW5kbyBNaWxlcyBNb3JhbGVzIHVuIHBlcnNvbmFnZ2lvIHBpdXR0b3N0byByZWNlbnRlLCBhcHBhcnNvIG5lbCAyMDExKSwgbGUgYWx0cmUgY29ubmVzc2lvbmkgc29ubyBkYSByaXRlbmVyZSAic3RvcmljaGUiIChhbGN1bmUgcmlzYWxlbnRpIGFsbGEgKmdvbGRlbiBhZ2UqKSBlZCDDqCBwb2NvIHBhbHVzaWJpbGUgY2hlIG5vbiBmb3NzZXJvIGdpw6AgZG9jdW1lbnRhdGUgY29ycmV0dGFtZW50ZSBuZWwgMjAxOCwgYW5ubyBkaSBjcmVhemlvbmUgZGVsIGRhdGFzZXQuDQpQZXJ0YW50bywgKippbCBkYXRhc2V0IHZpZW5lIHF1aSBjb25zaWRlcmF0byBjb21lIHVuIGdyYWZvIG5vbiBkaXJlemlvbmF0byBhZmZpbmNow6kgcmlmbGV0dGEgY29ycmV0dGFtZW50ZSBpIGxlZ2FtaSBkaSBwYXJ0bmVyc2hpcHMsKiogcGVyIHJpdXNjaXJlIGEgZGFyZSB1bidpbnRlcHJldGF6aW9uZSAic2Vuc2F0YSIgYWkgcmlzdWx0YXRpIHRyb3ZhdGkgc3VsIGdyYWZvLg0KDQpMJ2ludGVycHJldGF6aW9uZSBub24gb3JpZW50YXRhIGRlbCBncmFmbyB0cm92YSBjb25mZXJtYSBuZWwgcmVwb3NpdG9yeQ0KaHR0cHM6Ly9uZXR3b3Jrcy5za2V3ZWQuZGUvbmV0L21hcnZlbF9wYXJ0bmVyc2hpcHMjZm5yZWY6aWNvbg0KZG92ZSB2aWVuZSBjbGFzc2lmaWNhdG8gY29tZSBfVW5kaXJlY3RlZF8uDQoNCklsIGRhdGFzZXQgY29udGllbmUgYW5jaGUgdW4gKiphdHRyaWJ1dG8gY2F0ZWdvcmlhbGUgcGVyIGNpYXNjdW4gbm9kbyoqLCBkaXZpZGVuZG8gaSBwZXJzb25hZ2dpIGluICoqZXJvaSoqICgwKSwgKipjYXR0aXZpKiogKDEpIGUgcGVyc29uYWdnaSAiZ3JpZ2kiIGFsdHJpbWVudGkgZGV0dGkgKiphbnRpZXJvaSoqICgyKTsgcXVlc3RpIHVsdGltaSBwb3NzaWVkb25vIHF1YWxpdMOgIHNpYSBkZWdsaSBlcm9pIGNoZSBkZWkgY2F0dGl2aSwgb3NzaWEgcGVyc29uYWdnaSBzb2xpdGFtZW50ZSBwcml2aSBkaSBxdWFsaXTDoCBlcm9pY2hlIGNoZSBhIHZvbHRlIGNvbXBpb25vIGF6aW9uaSBtb3JhbG1lbnRlIGNvcnJldHRlLCBtYSBhZ2VuZG8gcHJpbmNpcGFsbWVudGUgcGVyIGludGVyZXNzZSBwZXJzb25hbGUgbyBpbiBtb2RpIGNoZSBzZmlkYW5vIGkgY29kaWNpIGV0aWNpIGNvbnZlbnppb25hbGkuDQoNCg0KIyMjIE9zc2VydmF6aW9uZQ0KSWwgZGF0YXNldCBwb3RyZWJiZSBlc3NlcmUgcmljb25kb3R0byBhZCB1bmEgc2l0dWF6aW9uZSByZWFsZSBpbW1hZ2luYW5kbyBjaGUgaSBwZXJzb25hZ2dpIE1hcnZlbCBzaWFubyBnbGkgKippbXBpZWdhdGkgZGkgdW4nYXppZW5kYSoqIGFmZmVyZW50aSBwcmluY2lwYWxtZW50ZSBhICoqZHVlIHJlcGFydGkgZGlzdGludGkqKiAoZXJvaSBlIGNhdHRpdmkpLCBtYWdyaSBpbiBjb21wZXRpemlvbmUsIHBpw7kgcXVhbGNoZSBhbHRyYSBmaWd1cmEgY29uIHVuIHJ1b2xvIG1lbm8gc3RydXR0dXJhdG8gKGdsaSBhbnRpZXJvaSksIGUgY2hlIHNpIHZvZ2xpYSBzdHVkaWFyZSBsZSAqKmludGVyYXppb25pIHN0cmV0dGUgdHJhIGkgdmFyaSBkaXBlbmRlbnRpLioqDQoNCiMjIENhcmljYW1lbnRvIGUgcHJlcGFyYXppb25lIGRlaSBkYXRpDQpJbCBkYXRhc2V0IHNjYXJpY2F0byDDqCBvcmdhbml6emF0byBpbiBkdWUgZGlzdGludGkgZmlsZTogYG5vZGVzLmNzdmAgZSBgZWRnZXMuY3N2YDoNCg0KLSBpbiBgbm9kZXMuY3N2YCBzb25vIHByZXNlbnRpIGxhIGNvbG9ubmEgYGdyb3VwYCAobGEgbmF0dXJhIGRlbCBwZXJzb25hZ2dpbyksIGBpZGAgKGlsIG5vbWUgZGVsIHBlcnNvbmFnZ2lvKSBlIGBzaXplYCAoaWwgbnVtZXJvIGRpIGNvbm5lc3Npb25pIGNoZSBjaWFzY3VuIHBlcnNvbmFnZ2lvIGhhIGNvbiBnbGkgYWx0cmkgZGVsIGRhdGFzZXQpLg0KLSBpbCBmaWxlIGBlZGdlcy5jc3ZgIGNvbnRpZW5lIGludmVjZSBsYSBjb2xvbm5hIGBzb3VyY2VgIGUgbGEgY29sb25uYSBgdGFyZ2V0YCBpbmRpY2FudGkgaWwgdmVyc28gKnByZXN1bnRvKiBkZWxsYSByZWxhemlvbmUgdHJhIGkgcGVyc29uYWdnaSByaXBvcnRhdGkgbmVsbGUgZHVlIGNvbG9ubmUuDQoNClNpIHByb2NlZGUgYWwgbG9ybyBjYXJpY2FtZW50byBpbiBSIGUgYWxsYSBzZW1wbGlmaWNhemlvbmUgZGkgYWxjdW5pIG5vbWksIGNlcmNhbmRvIGxhZGRvdmUgcG9zc2liaWxlIGRpIGxpbWl0YXJlIGxhIGx1bmdoZXp6YSBkaSBxdWVsbGkgcGnDuSBwcm9saXNzaSwgaW4gcGFydGljb2xhcmUgcmltdW92ZW5kbyBsZSBpbmRpY2F6aW9uaSB0cmEgcGFyZW50ZXNpIGNvbWUgaW4gYCdDeWNsb25lIChNYXJ2ZWwgQ29taWNzKSdgKS4NCg0KYGBge3J9DQpybShsaXN0PWxzKCkpDQpzZXR3ZCgiQzovVXNlcnMvZGFyaW8vRG9jdW1lbnRzL1Byb2plY3RzL01hc3Rlci9OZXR3b3Jrcy8iKQ0KDQpsaWJyYXJ5KGlncmFwaCkNCg0KbXlzZWVkID0gNjE3NA0KDQojIGxldHR1cmEgZmlsZSBjc3YgICANCm5vZGVzX2RhdGEgPSByZWFkLmNzdignbm9kZXMuY3N2JykgDQplZGdlc19kYXRhID0gcmVhZC5jc3YoJ2VkZ2VzLmNzdicpDQoNCiMgU2VtcGxpZmljYXppb25lIGRlaSBub21pIGx1bmdoaQ0Kb3JpZ19jaGFyID0gYygnQmxhY2tvdXQgKExpbGluKScsIA0KICAgICAgICAgICAgICAnQmxhY2tvdXQgKE1hcmN1cyBEYW5pZWxzKScsIA0KICAgICAgICAgICAgICAnSGF3a2V5ZSAoS2F0ZSBCaXNob3ApJywgDQogICAgICAgICAgICAgICdJcm9uIE1hbiAoVWx0aW1hdGUgTWFydmVsIGNoYXJhY3RlciknLCANCiAgICAgICAgICAgICAgJ1NwaWRlci1NYW4gKE1pbGVzIE1vcmFsZXMpJywgDQogICAgICAgICAgICAgICdTcGlkZXItV29tYW4gKEd3ZW4gU3RhY3kpJywgDQogICAgICAgICAgICAgICdTcGlkZXItV29tYW4gKEplc3NpY2EgRHJldyknDQopDQpuZXdfY2hhciA9IGMoJ0JsYWNrb3V0IEwuJywgDQogICAgICAgICAgICAgJ0JsYWNrb3V0IE0uRC4nLCANCiAgICAgICAgICAgICAnSGF3a2V5ZSBLLkIuJywgDQogICAgICAgICAgICAgJ0lyb24gTWFuIFUuTS5jLicsIA0KICAgICAgICAgICAgICdTcGlkZXItTWFuIE0uTS4nLCANCiAgICAgICAgICAgICAnU3BpZGVyLVdvbWFuIEcuUy4nLCANCiAgICAgICAgICAgICAnU3BpZGVyLVdvbWFuIEouRC4nDQopDQpmb3IgKG4gaW4gMTpsZW5ndGgob3JpZ19jaGFyKSkNCnsNCiAgIG5vZGVzX2RhdGEkaWRbbm9kZXNfZGF0YSRpZCA9PSBvcmlnX2NoYXJbbl1dID0gbmV3X2NoYXJbbl0NCiAgIGVkZ2VzX2RhdGEkc291cmNlW2VkZ2VzX2RhdGEkc291cmNlID09IG9yaWdfY2hhcltuXV0gPSBuZXdfY2hhcltuXQ0KICAgZWRnZXNfZGF0YSR0YXJnZXRbZWRnZXNfZGF0YSR0YXJnZXQgPT0gb3JpZ19jaGFyW25dXSA9IG5ld19jaGFyW25dDQp9DQojIHJpbW96aW9uZSBkZWwgY29udGVudXRvIHRyYSBwYXJlbnRlc2kNCm5vZGVzX2RhdGEkaWQgPSBnc3ViKCJcXHMqXFwoW15cXCldK1xcKSIsICIiLCBub2Rlc19kYXRhJGlkKQ0KZWRnZXNfZGF0YSRzb3VyY2UgPSBnc3ViKCJcXHMqXFwoW15cXCldK1xcKSIsICIiLCBlZGdlc19kYXRhJHNvdXJjZSkNCmVkZ2VzX2RhdGEkdGFyZ2V0ID0gZ3N1YigiXFxzKlxcKFteXFwpXStcXCkiLCAiIiwgZWRnZXNfZGF0YSR0YXJnZXQpDQoNCiMgY29udmVydG8gaSBjb2RpY2kgMCwxLDIgaW4gZ3JvdXAgaW4gdW5hIHZlcnNpb25lIHRlc3R1YWxlDQpncm91cCA9IG5vZGVzX2RhdGEkZ3JvdXANCm5vZGVzX2RhdGEkZ3JvdXAgPSBOVUxMICNyaW11b3ZvIGxhIGNvbG9ubmEgZ3JvdXAsIGNyZWEgcHJvYmxlbWkgYSBncmFwaF9mcm9tX2RhdGFfZnJhbWUNCmdyb3VwX2xhYmVscyA9IGMoImhlcm8iLCAidmlsbGFpbiIsICJhbnRpaGVybyIpDQpub2Rlc19kYXRhJGNhdGVnb3J5ID0gYXMuZmFjdG9yKGdyb3VwX2xhYmVsc1tncm91cCArIDFdKSAjcmVpbnNlcmlzY28gZ3JvdXAgY29tZSB2YXJpYWJpbGUgY2F0ZWdvcmljYQ0KDQojIGFzc29jaW8gZGVpIGNvbG9yaSBhbGxlIGNhdGVnb3JpZQ0KZ3JvdXBfY29sb3JzID0gYygnZGFya2dyYXknLCAnbGlnaHRibHVlJywgJ2xpZ2h0c2FsbW9uJykNCmdyb3VwX2NvbG9yc19ib3JkZXJzID0gYyhjKCdncmF5NTAnLCAnbGlnaHRibHVlMycsICdsaWdodHNhbG1vbjMnKSkNCiMgZ3JvdXBfY29sb3JzID0gYygnbGlnaHRibHVlJywgJ2xpZ2h0c2FsbW9uJywgJ2RhcmtncmF5JykgDQojIGdyb3VwX2NvbG9yc19ib3JkZXJzID0gYyhjKCdsaWdodGJsdWUzJywgJ2xpZ2h0c2FsbW9uMycsICdncmF5NTAnKSkNCg0KDQpub2RlX2NvbG9ycyA9IGdyb3VwX2NvbG9yc1thcy5udW1lcmljKG5vZGVzX2RhdGEkY2F0ZWdvcnkpXQ0KYm9yZGVyX2NvbG9ycyA9IGdyb3VwX2NvbG9yc19ib3JkZXJzW2FzLm51bWVyaWMobm9kZXNfZGF0YSRjYXRlZ29yeSldDQpgYGANCg0KDQoNCiMjIENyZWF6aW9uZSBncmFmbyBub24gZGlyZXppb25hdG8NCg0KYGBge3J9DQptYXJ2ZWxfVWdyYXBoID0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGQ9ZWRnZXNfZGF0YSwgdmVydGljZXM9bm9kZXNfZGF0YSwgZGlyZWN0ZWQ9RikNCg0Kc2V0LnNlZWQobXlzZWVkKQ0KI215X2xheW91dCA9IGxheW91dF93aXRoX2ZyIyhtYXJ2ZWxfVWdyYXBoLCBzdGFydC50ZW1wID0gMTAwLCBuaXRlcj0zMDAwKQ0KbGF5b3V0X2Z1bGwgPC0gbGF5b3V0X3dpdGhfZnIobWFydmVsX1VncmFwaCkNCg0KY29ubiA9IGNvbXBvbmVudHMobWFydmVsX1VncmFwaCkNCmlkX21heGNvbm4gPSB3aGljaChjb25uJGNzaXplPT0gbWF4KGNvbm4kY3NpemUpKQ0Kbm9kZXNfbWF4Y29ubiA9IHdoaWNoKGNvbm4kbWVtYmVyc2hpcCA9PSBpZF9tYXhjb25uKQ0KZWRnZXNfbWF4Y29ubiA8LSBFKG1hcnZlbF9VZ3JhcGgpWy5mcm9tKG5vZGVzX21heGNvbm4pICYgLnRvKG5vZGVzX21heGNvbm4pXQ0KDQpWKG1hcnZlbF9VZ3JhcGgpJG5hbWUgPSBub2Rlc19kYXRhJGlkDQpWKG1hcnZlbF9VZ3JhcGgpJGNhdGVnb3J5ID0gbm9kZXNfZGF0YSRjYXRlZ29yeQ0KVihtYXJ2ZWxfVWdyYXBoKSRzaXplID0gMy41DQpWKG1hcnZlbF9VZ3JhcGgpJGNvbG9yID0gbm9kZV9jb2xvcnMNClYobWFydmVsX1VncmFwaCkkZnJhbWUuY29sb3IgPSBib3JkZXJfY29sb3JzDQpWKG1hcnZlbF9VZ3JhcGgpJGxhYmVsLmNleCA9IDAuMw0KVihtYXJ2ZWxfVWdyYXBoKSRsYWJlbC5mYW1pbHkgPSAnc2FucycNClYobWFydmVsX1VncmFwaCkkbGFiZWwuY29sb3I9J2JsYWNrJw0KDQpFKG1hcnZlbF9VZ3JhcGgpJHdpZHRoID0gMSMwLjkNCkUobWFydmVsX1VncmFwaClbZWRnZXNfbWF4Y29ubl0kd2lkdGg9IDEuMg0KRShtYXJ2ZWxfVWdyYXBoKSRjb2xvciA9ICJkYXJrb2xpdmVncmVlbjMiIA0KRShtYXJ2ZWxfVWdyYXBoKVtlZGdlc19tYXhjb25uXSRjb2xvciA9ICJkYXJrb2xpdmVncmVlbiIgDQoNCnBhcihtYXIgPSBjKDAsIDAsIDAsIDApKQ0Kc2V0LnNlZWQobXlzZWVkKQ0KcGxvdChtYXJ2ZWxfVWdyYXBoLA0KICAgICBsYXlvdXQ9bGF5b3V0X2Z1bGwsDQogICAgIHZlcnRleC5jb2xvcj0gJ2Nvcm5zaWxrMicsICMnYnVybHl3b29kMScsDQogICAgIHZlcnRleC5mcmFtZS5jb2xvciA9ICdjb3Juc2lsazMnLCAjJ2J1cmx5d29vZDMnLA0KICAgICB2ZXJ0ZXguZnJhbWUud2lkdGggPSAwLjUsDQogICAgIGVkZ2Uud2lkdGggPSAwLjUsDQopDQoNCnNldC5zZWVkKG15c2VlZCkNCnBsb3QobWFydmVsX1VncmFwaCwNCiAgICAgbGF5b3V0PWxheW91dF9mdWxsLA0KICAgICB2ZXJ0ZXgubGFiZWwgPSBOQSwNCikNCmxlZ2VuZCgndG9wcmlnaHQnLCBsZWdlbmQgPSBsZXZlbHMobm9kZXNfZGF0YSRjYXRlZ29yeSksIGZpbGwgPSBncm91cF9jb2xvcnMsIGJ0eSA9ICduJykNCnBhcihtYXI9Yyg1LDQsNCwyKSswLjEpDQoNCm1hcnZlbF9VbWF4Y29ubiA9IGluZHVjZWRfc3ViZ3JhcGgobWFydmVsX1VncmFwaCwgdmlkcyA9IG5vZGVzX21heGNvbm4sIGltcGw9J2NyZWF0ZV9mcm9tX3NjcmF0Y2gnKQ0KYGBgDQpMYSByZXRlIHByZXNlbnRhIHVuYSBncm9zc2EgY29tcG9uZW50ZSBjb25uZXNzYSwgZSBwb2kgYWx0cmkgc290dG9ncmFmaSAgY29ubmVzc2kgbW9sdG8gcGnDuSBwaWNjb2xpLCByaWNvbmR1Y2liaWxpIHByb2JhYmlsbWVudGUgYSBwZXJzb25hZ2dpIG1pbm9yaSBvIHByZXNlbnRpIGluIHNlcmllIHBvY28gc29nZ2V0dGUgYSAqY3Jvc3Mtb3ZlciouDQoNCiMjIEFuYWxpc2kgZGVzY3JpdHRpdmEgYSBsaXZlbGxvIGRpIHJldGUNCiMjIyBEZW5zaXTDoA0KTGEgZGVuc2l0w6AgJFxyaG8gXGluIFswLDFdJCBwdcOyIGVzc2VyZSBjb25zaWRlcmF0YSB1bmEgc3RpbWEgZGVsbGEgKipwcm9iYWJpbGl0w6AgZGkgb3NzZXJ2YXJlIHVuIGFyY28gdHJhIG5vZGkgc2VsZXppb25hdGkgY2FzdWFsbWVudGUuKioNClxiZWdpbntlcXVhdGlvbn0NClxyaG8gPSBcZnJhY3tcdGV4dHsjIGRpIGFyY2hpfX17XHRleHR7bWF4ICMgZGkgYXJjaGl9fSBcaW4gWzAsMV0NClxlbmR7ZXF1YXRpb259DQoNCiMjIyBSZWNpcHJvY2l0w6ANCk5lbCBjYXNvIGRpIHVuYSAqKnJldGUgZGlyZXppb25hdGEqKiBsJ2luZGljZSBkaSByZWNpcHJvY2l0w6AgJFIkIHB1bnRhIGEgcXVhbnRpZmljYXJlIHF1YW50byBzaWEgZm9ydGUgbGEgKip0ZW5kZW56YSBhIHJpY2FtYmlhcmUgdW5hIHJlbGF6aW9uZToqKiDDqCBkZWZpbml0YSBjb21lIGxhIGZyYXppb25lICRSJCBkaSBsZWdhbWkgcmVjaXByb2NpDQpcYmVnaW57ZXF1YXRpb24qfQ0KUiA9IFxmcmFje1x0ZXh0eyMgZGkgYXJjaGkgcmVjaXByb2NpfX17XHRleHR7IyBkaSBhcmNoaX19IFxpbiBbMCwxXQ0KXGVuZHtlcXVhdGlvbip9DQoNCiogUiA9IDAgaW1wbGljYSBjaGUgdHV0dGUgbGUgcmVsYXppb25pIG9zc2VydmF0ZSB0cmEgaSBub2RpIG5lbGxhIHJldGUgbm9uIHNvbm8gcmVjaXByb2NoZS4NCiogUiA9IDEgaW1wbGljYSBjaGUgdHV0dGUgbGUgcmVsYXppb25pIG9zc2VydmF0ZSB0cmEgaSBub2RpIG5lbGxhIHJldGUgc29ubyByZWNpcHJvY2hlIChpbiB1bmEgcmV0ZSBub24gZGlyZXppb25hdGEsIHRhbGUgaW5kaWNlIHZhbGUgb3Z2aWFtZW50ZSBzZW1wcmUgMSkuDQoNCiMjIyBUcmFuc2l0aXZpdMOgDQpOZWxsJ2FtYml0byBkZWxsZSAqKnJldGkgbm9uIGRpcmV6aW9uYXRlLCoqIGxhIHRyYW5zaXRpdsOgIGluZGljYSBsYSAqKnRlbmRlbnphIGEgZm9ybWFyZSBsZWdhbWkgYSAzIHRyZSoqIHRyYSBpIG5vZGksIGVkIMOoIG1pc3VyYWJpbGUgYXR0cmF2ZXJzbyBpbCBjb2VmZmljaWVudGUgZGkgdHJhbnNpdGl2aXTDoCBvIGRpICpjbHVzdGVyaW5nKiAkQyQNClxiZWdpbntlcXVhdGlvbip9DQpDID0gXGZyYWN7XHRleHR7IyBkaSBjYW1taW5pICRjaGl1c2kkIGRpIGx1bmdoZXp6YSAyfX17XHRleHR7IyBkaSBjYW1taW5pIGRpIGx1bmdoZXp6YSAyfX0gXGluIFswLDFdDQpcZW5ke2VxdWF0aW9uKn0NCg0KIyMjIEFzc29ydGF0aXZpdMOgDQoqIEwnKiphc3NvcnRhdGl2aXTDoCoqIG8gKm9tb2ZpbGlhKiBtb2RlbGxhIGxhIHBvc3NpYmlsaXTDoCBkaSBvc3NlcnZhcmUgbGVnYW1pIHRyYSBub2RpIGNoZSBzb25vIHNpbWlsaSB0cmEgbG9ybyBwaXV0dG9zdG8gY2hlIHRyYSBxdWVsbGkgY2hlIG5vbiBsbyBzb25vLg0KKiBMYSAqKmRpc3Nhc3NvcnRhdGl2aXTDoCoqIG8gKmV0ZXJvZmlsaWEqIGluZGljYSBsbyBzY2hlbWEgb3Bwb3N0bw0KDQpMaW1pdGFuZG8gaWwgY29uY2V0dG8gYWQgdW5hICoqcmV0ZSBub24gZGlyZXppb25hdGEgKG5vbiBwZXNhdGEpKiogY29uICRtJCBhcmNoaSAkXHt1X3tpan1cfSBcaW4gXHswLDFcfSQsIGUgY29uICoqYXR0cmlidXRpIG5vZGFsaSBub21pbmFsaSoqICgqZXJvaSosICpjYXR0aXZpKiBlICphbnRpZXJvaSopICRjX2kkLCBsJyoqaW5kaWNlIGRpIGFzc29ydGF0aXZpdMOgKiogJHIkIMOoIGRlZmluaXRvIGF0dHJhdmVyc28gbGEgKiptb2R1bGFyaXTDoCoqICRRJCwgY2hlIGNvbmZyb250YSBpbCAqKm51bWVybyBkaSBhcmNoaSBhbGwnaW50ZXJubyBkZWxsZSBjb211bml0w6AqKiBjb24gcXVlbGxvIGF0dGVzbyBpbiB1bmEgKipyZXRlIGNhc3VhbGUqKg0KXGJlZ2lue2FsaWduKn0NCnIgJj0gXGZyYWN7UX17UV97XG1heH19IFxpbiBbLTEsMV1cXA0KUSAmPSBcZnJhY3sxfXsybX0gXHN1bV97aWp9ICB1X3tpan0gLSBcZnJhY3trX2kga19qfXsybX0gXGRlbHRhKGNfaSwgY19qKVxcDQpcZGVsdGEoY19pLGNfaikgJj0gXGJlZ2lue2Nhc2VzfSAxICYgY19pPWNfaiBcXCAwICYgY19pIFxuZXEgY19qXGVuZHtjYXNlc30NClxlbmR7YWxpZ24qfQ0KZXNzZW5kbyAka19uJCBpbCAqKmdyYWRvKiogKGNpb8OoIGlsICMgZGkgYXJjaGkgaW5jaWRlbnRpKSBkZWwgbm9kbyAkbiQuDQoNCkluIFIgw6ggZGlzcG9uaWJsZSBsYSBmdW56aW9uZSAqYXNzb3J0YXRpdml0eV9ub21pbmFsKCkqIGNoZSBjYWxjb2xhICRyJCBhdHRyYXZlcnNvIGxhICoqZnJhemlvbmUgJGVfe2lqfSQgZGkgYXJjaGkgY2hlIGNvbm5ldHRvbm8gaSBub2RpIGRpIF90aXBvXyAkY19pJCBlIF90aXBvXyAkY19qJCoqOg0KXGJlZ2lue2FsaWduKn0NCnIgICAmPSBcZnJhY3tcc3VtX2kgZV97aWl9IC1cc3VtX2kgYV9pIGJfaX17MS1cc3VtX2kgYV9pIGJfaX1cXA0KYV9pICY9IFxzdW1faiBlX3tpan0gXFwNCmJfaiAmPSBcc3VtX2kgZV97aWp9DQpcZW5ke2FsaWduKn0NCg0KDQpgYGB7cn0NCg0KIyBncmFmbyBOT04gRElSRVpJT05BVE8NCnJob191ID0gZWRnZV9kZW5zaXR5KCBtYXJ2ZWxfVWdyYXBoKQ0KUl91ID0gcmVjaXByb2NpdHkoICBtYXJ2ZWxfVWdyYXBoKQ0KQ191ID0gdHJhbnNpdGl2aXR5KCBtYXJ2ZWxfVWdyYXBoKQ0Kcl91ID0gYXNzb3J0YXRpdml0eV9ub21pbmFsKG1hcnZlbF9VZ3JhcGgsIFYobWFydmVsX1VncmFwaCkkY2F0ZWdvcnkpDQoNCnJob19jID0gZWRnZV9kZW5zaXR5KCBtYXJ2ZWxfVW1heGNvbm4pDQpSX2MgPSByZWNpcHJvY2l0eSggIG1hcnZlbF9VbWF4Y29ubikNCkNfYyA9IHRyYW5zaXRpdml0eSggbWFydmVsX1VtYXhjb25uKQ0Kcl9jID0gYXNzb3J0YXRpdml0eV9ub21pbmFsKG1hcnZlbF9VbWF4Y29ubiwgVihtYXJ2ZWxfVW1heGNvbm4pJGNhdGVnb3J5KQ0KDQp0YWJsZV9ncmFwaCA8LSBkYXRhLmZyYW1lKA0KICAgIldob2xlIiA9IGMocmhvX3UsIFJfdSwgQ191LCByX3UpLA0KICAgIk1heGNvbm4iID0gYyhyaG9fYywgUl9jLCBDX2MsIHJfYyksDQogICByb3cubmFtZXMgPSBjKCJEZW5zaXTDoCIsICJSZWNpcHJvY2l0w6AiLCAiVHJhbnNpdGl2aXTDoCIsICJBc3NvcnRhdGl2aXTDoCIpDQopDQp0YWJsZV9ncmFwaCRXaG9sZSA9IHNwcmludGYoIiUuM2YiLCB0YWJsZV9ncmFwaCRXaG9sZSkNCnRhYmxlX2dyYXBoJE1heGNvbm4gPSBzcHJpbnRmKCIlLjNmIiwgdGFibGVfZ3JhcGgkTWF4Y29ubikNCg0KDQoodGFibGVfZ3JhcGgpDQpgYGANCiMjIyMgT3NzZXJ2YXppb25pDQoqIGxhICoqZGVuc2l0w6AqKiBhdW1lbnRhIG5lbCBjYXNvIGRlbCBzb3R0b2dyYWZvIG1hc3NpbWFtZW50ZSBjb25uZXNzbyBkYXRvIGlsIG1pbm9yIG51bWVybyBkaSBhcmNoaSAibWFuY2FudGkiIHJpc3BldHRvIGFsIGdyYWZvIGNvbXBsZXRvIGNvbiB0dXR0ZSBsZSBhbHRyZSBjb21wb25lbnRpDQoqIGxhICoqYmFzc2EgdHJhbnNpdGl2aXTDoCoqIGluIGVudHJhbWJpIGkgY2FzaSByaWZsZXR0ZSBsYSBuYXR1cmEgZGVpIGxlZ2FtaSBkZWZpbml0aSBkYWwgY29uY2V0dG8gZGkgInBhcnRuZXJzaGlwcyIsIHRyYXR0YW5kb3NpIGNpb8OoIGRpIGFzc29jaWF6aW9uaSBzdHJldHRlICh0aXBvIElyb24tTWFuIGUgV2FyLU1hY2hpbmUsIGNoZSBvcGVyYW5vIHNwZXNzbyBpbiBjb3BwaWEpIGRpdmVyc2UgZGFsbGUgY29sbGFib3JhemlvbmkgaW4gdGVhbSAodGlwbyBBdmVuZ2VycykNCiogbCcqKmFzc29ydGF0aXZpdMOgIHBvc2l0aXZhKiogKG5vbiB0cmFzY3VyYWJpbGUsIGluIHF1YW50byAqKnN1cGVyaW9yZSBhIDAuNioqIHNpYSBuZWwgZ3JhZm8gY29tcGxldG8gY2hlIHBlciBpbCBzb3R0b2dyYWZvIG1hZ2dpb3JtZW50ZSBjb25uZXNzbykgcmlmbGV0dGUgbGEgbmF0dXJhIGRlbGwnYXR0cmlidXRvLCBlZCDDqCBpbmRpY2UgY2hlIGkgY2F0dGl2aSBzaSBsZWdhbm8gc29saXRhbWVudGUgYWQgYWx0cmkgY2F0dGl2aSBlIGkgYnVvbmkgYWQgYWx0cmkgYnVvbmkuDQoNCiMjIEFuYWxpc2kgZGVzY3JpdHRpdmEgYSBsaXZlbGxvIGRpIG5vZGkNCkNpIGNvbmNlbnRyaWFtbyBxdWkgc3VsbGEgKipjb21wb25lbnRlIG1hZ2dpb3JtZW50ZSBjb25uZXNzYSBgbWFydmVsX1VtYXhjb25uYDoqKiBkZWwgZ3JhZm8gbm9uIGRpcmV6aW9uYXRvIGBtYXJ2ZWxfVWdyYXBoYCBjb24gYXJjaGkgJHVfe2lqfSBcaW4gXHswLDFcfSQ6IHF1ZXN0byBzaWEgcGVyY2jDqSBnbGkgYWx0cmkgc290dG9ncmFmaSBzb25vIHJlbGF0aXZpIGEgcGVyc29uYWdnaSBvIHNlcmllIG1vbHRvIG1lbm8gcmlsZXZhbnRpIG5lbGwndW5pdmVyc28gTWFydmVsLCBzaWEgcGVyY2jDqSBzaSB2dW9sZSBjb25mcm9udGFyZSBpbiBtb2RvICJlcXVvIiBpIHRyZSBkaXZlcnNpIGNvbmNldHRpIGRpIGNlbnRyYWxpdMOgIGRpIHNlZ3VpdG8gZGVmaW5pdGkuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQobXlzZWVkKQ0KaW5kaWNlc19tYXhjb25uX2luX3VncmFwaCA9IG1hdGNoKFYobWFydmVsX1VtYXhjb25uKSRuYW1lLCBWKG1hcnZlbF9VZ3JhcGgpJG5hbWUpDQpsYXlvdXRfbWF4Y29ubiA9IGxheW91dF9mdWxsW2luZGljZXNfbWF4Y29ubl9pbl91Z3JhcGgsIF0NCnNldC5zZWVkKG15c2VlZCkNCnBhcihtYXIgPSBjKDAsIDAsIDAsIDApKQ0Kc2V0LnNlZWQobXlzZWVkKQ0KcGxvdCgNCiAgIG1hcnZlbF9VbWF4Y29ubiwNCiAgIGxheW91dCA9IGxheW91dF9tYXhjb25uLA0KICAgZWRnZS53aWR0aCA9IDAuNQ0KKQ0KbGVnZW5kKCd0b3ByaWdodCcsIGxlZ2VuZCA9IGxldmVscyhub2Rlc19kYXRhJGNhdGVnb3J5KSwgZmlsbCA9IGdyb3VwX2NvbG9ycywgYnR5ID0gJ24nKQ0KcGFyKG1hcj1jKDUsNCw0LDIpKzAuMSkNCmBgYA0KDQojIyMgRGVncmVlIENlbnRyYWxpdHkgKGNlbnRyYWxpdMOgIGRpIGdyYWRvKQ0KSWwgKipncmFkbyoqIChkZWdyZWUpICRcemV0YV9pXmQkIG8gbGEgKmNlbnRyYWxpdMOgIGRpIGdyYWRvKiBkaSB1biBub2RvICRpJCBpbmRpY2EgaWwgKipudW1lcm8gZGkgbGVnYW1pIGNoZSBjb2ludm9sZ29ubyAkaSQqKg0KXGJlZ2lue2VxdWF0aW9uKn0NClx6ZXRhX2leZCA9IFxzdW1fe2lqfSB1X3tpan0NClxlbmR7ZXF1YXRpb24qfQ0KUGVyIHVuYSByZXRlIGNvbiAkbiQgbm9kaSwgJFx6ZXRhX2leZCBcaW4gWzAsbi0xXSQNCg0KTGEgc3VhIHZlcnNpb25lIG5vcm1hbGl6emF0YSAkXHRpbGRlXHpldGFfaV5kJCDDqCBxdWluZGkgZGVmaW5pdGEgY29tZQ0KXGJlZ2lue2VxdWF0aW9uKn0NClx0aWxkZVx6ZXRhX2leZCA9IFxmcmFje1xzdW1fe2lqfSB1X3tpan19e24tMX0gXGluIFswLDFdDQpcZW5ke2VxdWF0aW9uKn0NCg0KKipMYSBjZW50cmFsaXTDoCBkaSBncmFkbyBmb3JuaXNjZSBpbmZvcm1hemlvbmkgc3VsbCdpbmZsdWVuemEgZGlyZXR0YSBkZWwgbm9kbyBuZWxsYSByZXRlIGUgc3VsIHN1byBhY2Nlc3NvIGFsbGUgaW5mb3JtYXppb25pIGRpIHByaW1hIG1hbm8qKg0KYGBge3J9DQpwbG90X2NlbnRyYWxpdHkgPC0gZnVuY3Rpb24oY2VudHJfbm9ybSwgdGhyX3EsIHNjYWxlVGV4dD1GKQ0Kew0KICAgdGhyX25vcm0gPSBxdWFudGlsZShjZW50cl9ub3JtLCB0aHJfcSkNCiAgIHByaW50KHRocl9ub3JtKQ0KICAgbmFtZW9mZiA9IHdoaWNoKGNlbnRyX25vcm08dGhyX25vcm0pDQogICBzaG93bmFtZSA9IFYobWFydmVsX1VtYXhjb25uKSRuYW1lDQogICBzaG93bmFtZVtuYW1lb2ZmXSA9IE5BDQogICANCiAgIGNleFRleHQgPSAwLjUNCiAgIGlmIChzY2FsZVRleHQ9PVQpDQogICAgICBjZXhUZXh0ID0gY2VudHJfbm9ybSoxMg0KICAgICAgDQogICBwYXIobWFyPWMoMCwwLDAsMCkpDQogICBwbG90KG1hcnZlbF9VbWF4Y29ubiwNCiAgICAgICAgbGF5b3V0PWxheW91dF9tYXhjb25uLA0KICAgICAgICB2ZXJ0ZXguc2l6ZSA9IGNlbnRyX25vcm0qMjIsDQogICAgICAgIHZlcnRleC5sYWJlbCA9IHNob3duYW1lLA0KICAgICAgICB2ZXJ0ZXgubGFiZWwuY2V4ID0gY2V4VGV4dCwjMC41LA0KICAgICAgICBlZGdlLndpZHRoID0gMC40DQogICApDQogICBwYXIobWFyPWMoNSw0LDQsMikrMC4xKSAgIA0KfQ0KDQpkZWcgPSBkZWdyZWUobWFydmVsX1VtYXhjb25uKQ0KZGVnX25vcm0gPSBkZWdyZWUobWFydmVsX1VtYXhjb25uLCBub3JtYWxpemVkID0gVCkNCg0KcGFyKG1hcj1jKDQsNCwzLDApKQ0KaGlzdChkZWdfbm9ybSwgZnJlcT1GLCBtYWluPSdkZWdyZWUgY2VudHJhbGl0eScsIGNvbD0ncGFsZWdyZWVuMycsIGJvcmRlcj0ncGFsZWdyZWVuNCcpDQpncmlkKGx0eT0nc29saWQnLCBsd2Q9MC41LCBjb2w9J3doaXRlJywgbng9TkEsIG55PU5VTEwpDQpwYXIobWFyPWMoNSw0LDQsMikrMC4xKQ0KDQp0aHJfcSA9IDAuOTUNCnBsb3RfY2VudHJhbGl0eShkZWdfbm9ybSwgdGhyX3EsIFQpDQoNCm9yZCA9IG9yZGVyKGRlZ19ub3JtLCBkZWNyZWFzaW5nID0gVCkNCmRlZ3JhbmsgPSBkYXRhLmZyYW1lKG5hbWUgPSBWKG1hcnZlbF9VbWF4Y29ubikkbmFtZVtvcmRdLCBkZWdyZWUgPSBkZWdbb3JkXSwgZGVnbm9ybT1kZWdfbm9ybVtvcmRdKQ0KKGRlZ3JhbmspDQpgYGANCg0KIyMjIENsb3NlbmVzcyBjZW50cmFsaXR5IChjZW50cmFsaXTDoCBkaSBwcm9zc2ltaXTDoCkNClBlciB2YWx1dGFyZSBsYSAqcHJvc3NpbWl0w6AqIGRpIHVuIG5vZG8gcmlzcGV0dG8gYWdsaSBhbHRyaSBub2RpIG9jY29ycmUgcHJpbWEgaW50cm9kdXJyZSBpbnRyb2R1cnJlIGxhICoqZmFybmVzcyoqICgqbG9udGFuYW56YSopICRsX2kkIGRpIHVuIG5vZG8NClxiZWdpbntlcXVhdGlvbip9DQpsX2kgPSBcc3VtIGRfe2lqfQ0KXGVuZHtlcXVhdGlvbip9DQplc3NlbmRvICRkX3tpan0kIGxhIGx1bmdoZXp6YSBkZWwgKipjYW1taW5vIHBpw7kgYnJldmUqKiB0cmEgaWwgbm9kbyAkaSQgZWQgaWwgbm9kbyAkaiQuDQoNCkxhICoqY2xvc2VuZXNzKiogJFx6ZXRhX2leYyQgZGkgdW4gbm9kbyAkaSQgdmFsZQ0KXGJlZ2lue2VxdWF0aW9uKn0NClx6ZXRhX2kgPSBcZnJhY3sxfXtsX2l9DQpcZW5ke2VxdWF0aW9uKn0NCg0KUG9pY2jDqSBpbCBtYXNzaW1vIGRpICRcemV0YV9pXmMkIHNpIHZlcmlmaWNhIHF1YW5kbyAkaSQgw6ggY29ubmVzc28gYSB0dXR0aSBxdWFudGkgZ2xpIGFsdHJpIG5vZGkgZGVsbGEgcmV0ZSAoKmNvbmZpZ3VyYXppb25lIGEgc3RlbGxhKiksIGNpb8OoICRcemV0YV97XG1heH1eYyA9IDEvXHN1bV97aSBcbmVxIGp9IDEgPSAxLyhuLTEpJCwgbGEgY2xvc2VuZXNzIG5vcm1hbGl6emF0YSAkXHRpbGRlXHpldGFfaV5jJCB2YWxlDQpcYmVnaW57ZXF1YXRpb24qfQ0KXHRpbGRlXHpldGFfaV5jID0gXGZyYWN7XHpldGFfaV5jfXtcemV0YV97XG1heH1eY30gPSBcZnJhY3tuLTF9e2xfaX0gXGluIFswLDFdDQpcZW5ke2VxdWF0aW9uKn0NCg0KKipMYSBjbG9zZW5lc3Mgw6ggdXRpbGl6emF0YSBwZXIgaW5kaXZpZHVhcmUgbm9kaSBzdHJhdGVnaWNpIHBlciBsYSB0cmFzbWlzc2lvbmUgZGkgaW5mb3JtYXppb25pOiBpbmRpY2Egc2UgdW4gbm9kbyBoYSB1biBhY2Nlc3NvIHJhcGlkbyBlIGRpcmV0dG8gYWdsaSBhbHRyaSBub2RpKioNCg0KIyMjIyBPc3NlcnZhemlvbmUNClBvaWNow6kgIGluIGBtYXJ2ZWxfVWdyYXBoYCBub24gZXNpc3RlIHNlbXByZSBhbG1lbm8gdW4gY2FtbWlubyB0cmEgdHV0dGUgbGUgY29wcGllIGRpIG5vZGkpLCAqKmFwcGxpY2hpYW1vIGlsIGNvbmNldHRvIGRpIGNsb3NlbmVzcyBjZW50cmFsaXR5IHNvbG8gYWwgc290dG9ncmFmbyBjb25uZXNzbyBwacO5IG51bWVyb3NvLioqDQoNCg0KYGBge3J9DQpjbG8gPSBjbG9zZW5lc3MobWFydmVsX1VtYXhjb25uKQ0KY2xvX25vcm0gPSBjbG9zZW5lc3MobWFydmVsX1VtYXhjb25uLCBub3JtYWxpemVkID0gVCkNCg0KcGFyKG1hcj1jKDQsNCwzLDApKQ0KaGlzdChjbG9fbm9ybSwgZnJlcT1GLCBtYWluPSdjbG9zZW5lc3MgY2VudHJhbGl0eScsIGNvbD0ncGFsZWdyZWVuMycsIGJvcmRlcj0ncGFsZWdyZWVuNCcpDQpncmlkKGx0eT0nc29saWQnLCBsd2Q9MC41LCBjb2w9J3doaXRlJywgbng9TkEsIG55PU5VTEwpDQpwYXIobWFyPWMoNSw0LDQsMikrMC4xKQ0KDQpwbG90X2NlbnRyYWxpdHkoY2xvX25vcm0sIHRocl9xKQ0KDQpvcmQgPSBvcmRlcihjbG9fbm9ybSwgZGVjcmVhc2luZyA9IFQpDQpjbG9yYW5rID0gZGF0YS5mcmFtZShuYW1lID0gVihtYXJ2ZWxfVW1heGNvbm4pJG5hbWVbb3JkXSwgY2xvc2VuZXNzID0gY2xvW29yZF0sIGNsb25vcm09Y2xvX25vcm1bb3JkXSkNCihjbG9yYW5rKQ0KYGBgDQoNCiMjIyBCZXR3ZWVubmVzcyBjZW50cmFsaXR5IChjZW50cmFsaXTDoCBkaSBpbnRlcm1lZGlhemlvbmUpDQpMJ2lkZWEgYWxsYSBiYXNlIGRlbGxhIGJldHdlZW5uZXNzIGNlbnRyYWxpdHkgw6ggY2hlIHVuICoqbm9kbyBzaWEgbWFnZ2lvcm1lbnRlIGNlbnRyYWxlIHBlciB1bmEgZGF0YSByZXRlIHF1YW50byBwacO5IHNpIHRyb3ZhIHRyYSBtb2x0aSBhbHRyaSBub2RpLioqDQoNCkxhIGJldHdlZW5uZXNzICRcemV0YV9pXmIkIGRpIHVuIG5vZG8gJGkkIMOoIGxhICoqZnJhemlvbmUgZGVpIGNhbW1pbmkgbWluaW1pIGNoZSBwYXNzYW5vIGF0dHJhdmVyc28gJGkkKioNClxiZWdpbnthbGlnbip9DQpcemV0YV9pXmIgJj0gXHN1bV97aj4xfSBcc3VtX3trPmp9IFxmcmFje25fe2prfV5pfXtnX3tqa319IFxxcXVhZCBcdGV4dHNme2Nvbn1cXA0KXGZyYWN7bl97amt9Xml9e2dfe2prfX0gJj0gMCBccXVhZFx0ZXh0c2Z7c2V9XHF1YWQgbl97amt9XmkgPSBnX3tqa30gPSAwIFxcDQpuX3tqa31eaSAmPSBcdGV4dHNmeyMgZGkgY2FtbWluaSBtaW5pbWkgdHJhIGkgbm9kaSAkaiQgZSAkayQgcGFzc2FudGkgYXR0cmF2ZXJzbyAkaSR9XFwNCmdfe2prfSAmPSBcdGV4dHNmeyMgZGkgY2FtbWluaSBtaW5pbWkgdHJhIGkgbm9kaSAkaiQgZSAkayQgaW4gdG90YWxlfQ0KXGVuZHthbGlnbip9DQoNClBvaWNow6kgaWwgbWFzc2ltbyBkaSAkXHpldGFfaV5iJCBzaSB2ZXJpZmljYSBxdWFuZG8gJGkkIHNpIHRyb3ZhIHN1IHR1dHRpIGkgcGVyY29yc2kgZ2VvZGV0aWNpIGNoZSBjb2xsZWdhbm8gb2duaSBjb3BwaWEgZGkgYWx0cmkgbm9kLCBvc3NpYSBzaSBoYQ0KXGJlZ2lue2VxdWF0aW9ufQ0Kbl97amt9Xmk9Z197amt9XGVuc3BhY2UgXGZvcmFsbFwsIChqLGspIFxxdWFkIFxSaWdodGFycm93IFxxdWFkIFx6ZXRhX3tcbWF4fV5iID0gXHN1bV97aj5pfSBcc3VtX3trPml9IDEgPSBcZnJhY3sobi0xKShuLTIpfXsyfQ0KXGVuZHtlcXVhdGlvbn0NCmlsIHZhbG9yZSBub3JtYWxpenphdG8gJFxoYXRcemV0YV9pXmIkIMOoIGRlZmluaXRvIGNvbWUNClxiZWdpbntlcXVhdGlvbn0NClx0aWxkZVx6ZXRhX2leYiA9IFxmcmFje1x6ZXRhX2leYn17KG4tMSkobi0yKX0NClxlbmR7ZXF1YXRpb259DQoNCioqTGEgYmV0d2Vlbm5lc3MgY2VudHJhbGl0eSBhaXV0YSBhZCBpZGVudGlmaWNhcmUgbm9kaSBzdHJhdGVnaWNpIG8gdnVsbmVyYWJpbGkgaW4gdW5hIHJldGU6IGluZGljYSBxdWFudG8gdW4gdmVydGljZSBzaWEgY3J1Y2lhbGUgY29tZSAic25vZG8iIHBlciBpIGZsdXNzaSBkaSBpbmZvcm1hemlvbmkgbyBjb25uZXNzaW9uaSBuZWwgZ3JhZm8uKioNCg0KIyMjIyBPc3NlcnZhemlvbmUNCkNvaW52b2xnZW5kbyBkaSBudW92byBpIGNhbW1pbmkgbWluaW1pLCBhbmNoZSBpbiBxdWVzdG8gY2FzbywgcHJlbmRpYW1vIHF1aSBpbiBjb25zaWRlcmF6aW9uZSBzb2xvIGlsIHNvdHRvZ3JhZm8gY29ubmVzc28gcGnDuSBudW1lcm9zby4NCg0KYGBge3J9DQpiZXQgPSBiZXR3ZWVubmVzcyhtYXJ2ZWxfVW1heGNvbm4pDQpiZXRfbm9ybSA9IGJldHdlZW5uZXNzKG1hcnZlbF9VbWF4Y29ubiwgbm9ybWFsaXplZCA9IFQpDQpwYXIobWFyPWMoNCw0LDMsMCkpDQpoaXN0KGJldF9ub3JtLCBmcmVxPUYsIG1haW49J2JldHdlZW5uZXNzIGNlbnRyYWxpdHknLCBjb2w9J3BhbGVncmVlbjMnLCBib3JkZXI9J3BhbGVncmVlbjQnKQ0KZ3JpZChsdHk9J3NvbGlkJywgbHdkPTAuNSwgY29sPSd3aGl0ZScsIG54PU5BLCBueT1OVUxMKQ0KcGFyKG1hcj1jKDUsNCw0LDIpKzAuMSkNCnBsb3RfY2VudHJhbGl0eShiZXRfbm9ybSwgdGhyX3EpDQoNCm9yZCA9IG9yZGVyKGJldF9ub3JtLCBkZWNyZWFzaW5nID0gVCkNCmJldHJhbmsgPSBkYXRhLmZyYW1lKG5hbWUgPSBWKG1hcnZlbF9VbWF4Y29ubikkbmFtZVtvcmRdLCBiZXR3ZWVubmVzcyA9IGNsb1tvcmRdLCBiZXRub3JtPWJldF9ub3JtW29yZF0pDQooYmV0cmFuaykNCmBgYA0KDQojIyMgTm9kaSBpbXBvcnRhbnRpDQoNCmBgYHtyIGNlbnRyYWxpdGllc0NtcCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0NCnRvcG5vZGVzIDwtIGZ1bmN0aW9uKGNlbnRyX25vcm0sIHRocl9xKQ0Kew0KICAgdGhyX25vcm0gPSBxdWFudGlsZShjZW50cl9ub3JtLCB0aHJfcSkNCiAgIGlkb24gPSB3aGljaChjZW50cl9ub3JtPj10aHJfbm9ybSkNCiAgIHJldHVybihpZG9uKQ0KfQ0KDQpoYyA9IHF1YW50aWxlKGNsb19ub3JtLCB0aHJfcSkNCmhiID0gcXVhbnRpbGUoYmV0X25vcm0sIHRocl9xKQ0KDQppZHRvcF9kZWcgPSB0b3Bub2RlcyhkZWdfbm9ybSwgdGhyX3EpDQppZHRvcF9jbG8gPSB0b3Bub2RlcyhjbG9fbm9ybSwgdGhyX3EpDQppZHRvcF9iZXQgPSB0b3Bub2RlcyhiZXRfbm9ybSwgdGhyX3EpDQoNCmlkdG9wID0gdW5pb24odW5pb24oaWR0b3BfZGVnLCBpZHRvcF9jbG8pLCBpZHRvcF9iZXQpDQppZHhvciA9IHNldGRpZmYodW5pb24oaWR0b3BfZGVnLCB1bmlvbihpZHRvcF9jbG8sIGlkdG9wX2JldCkpLCBpbnRlcnNlY3QoaWR0b3BfZGVnLCBpbnRlcnNlY3QoaWR0b3BfY2xvLCBpZHRvcF9iZXQpKSkNCmlkYmVzdCA9IGludGVyc2VjdChpbnRlcnNlY3QoaWR0b3BfZGVnLCBpZHRvcF9jbG8pLCBpZHRvcF9iZXQpDQoNCnBhcihtYXIgPSBjKDQsIDQsIDIsIDEpKQ0Kc2NhbGRlZyA9IDYNCnNjb3JlcyA9IGRhdGEuZnJhbWUobmFtZSA9VihtYXJ2ZWxfVW1heGNvbm4pJG5hbWUsIGRlZz1kZWcsIGNsbz1jbG9fbm9ybSwgYmV0PWJldF9ub3JtKQ0KcGxvdChzY29yZXMkYmV0LCBzY29yZXMkY2xvLCANCiAgICAgeWxhYiA9ICdDbG9zZW5lc3MnLCANCiAgICAgeGxhYiA9ICdCZXR3ZWVubmVzcycsDQogICAgIG1haW4gPSAnTm9kZSBjZW50cmFsaXRpZXMnLA0KICAgICB4bGltID0gYygwLDAuOSksDQogICAgIGNleCA9IHNjb3JlcyRkZWcvc2NhbGRlZywgDQogICAgIHBjaCA9IDIxLCANCiAgICAgY29sID0gVihtYXJ2ZWxfVW1heGNvbm4pJGNvbG9yLA0KICAgICBiZyA9ICJ3aGl0ZSIsDQogICAgIGJ0eT0nbicNCikNCnBvaW50cyhzY29yZXMkYmV0W2lkdG9wX2RlZ10sIHNjb3JlcyRjbG9baWR0b3BfZGVnXSwgY2V4PXNjb3JlcyRkZWdbaWR0b3BfZGVnXS9zY2FsZGVnLCANCiAgICAgICBwY2ggPSAyMSwgY29sPVYobWFydmVsX1VtYXhjb25uKSRjb2xvcltpZHRvcF9kZWddLCBiZz1WKG1hcnZlbF9VbWF4Y29ubikkY29sb3JbaWR0b3BfZGVnXSkNCmFibGluZShoID0gaGMsIGx0eSA9IDIpDQphYmxpbmUodiA9IGhiLCBsdHkgPSAyKQ0KZ3JpZCgpDQp0ZXh0KHkgPSBoYywgeCA9IDAuODYsICcwLjk1IHF1YW50aWxlJywgcG9zID0gMSwgY2V4PTAuNykNCnRleHQoeCA9IGhiLCB5ID0gMC4yMTUsICcwLjk1IHF1YW50aWxlJywgcG9zID0gNCwgY2V4PTAuNykNCnRleHQoY2V4PTAuNSwgc2NvcmVzJGJldFtpZHhvcl0sIHNjb3JlcyRjbG9baWR4b3JdLCBsYWJlbHM9c2NvcmVzJG5hbWVbaWR4b3JdLCBwb3M9NCkNCnRleHQoY2V4PTAuNiwgc2NvcmVzJGJldFtpZGJlc3RdLCBzY29yZXMkY2xvW2lkYmVzdF0sIGxhYmVscz1zY29yZXMkbmFtZVtpZGJlc3RdLCBwb3M9NCwgZm9udD0yKQ0KDQptaW5kZWcgPSBtaW4oc2NvcmVzJGRlZykNCm1lZGRlZyA9IDYNCm1heGRlZyA9IG1heChzY29yZXMkZGVnKQ0KbGVnZW5kX2RlZyA9IGMobWluZGVnL3NjYWxkZWcsIG1lZGRlZy9zY2FsZGVnLCBtYXhkZWcvc2NhbGRlZykgDQpsZWdlbmRfZGVndGV4dCA9IGMoJzEgKG1pbiknLCAnNiAoMC45NXEuKScsICcxMiAobWF4KScpDQoNCmxlZ2VuZCgndG9wcmlnaHQnLCBjZXggPSAwLjcsIGJ0eT0nbicsDQogICAgICAgbGVnZW5kID0gbGVnZW5kX2RlZ3RleHQsDQogICAgICAgcHQuY2V4ID0gbGVnZW5kX2RlZywNCiAgICAgICBwY2g9MjEsIHRpdGxlPSdkZWdyZWUgc2NhbGUnKQ0KbGVnZW5kKCd0b3BsZWZ0JywgY2V4ID0gMC43LCBidHk9J24nLA0KICAgICAgIGxlZ2VuZCA9IGMoJ2FudGloZXJvZXMnLCAnaGVyb2VzJywgJ3ZpbGxhaW5zJyksDQogICAgICAgcHQuY2V4ID0gMSwNCiAgICAgICBwY2g9MTUsDQogICAgICAgY29sID0gZ3JvdXBfY29sb3JzKSAjdGl0bGUgPSAnY2F0ZWdvcnk6Jw0KbGVnZW5kKCdib3R0b21yaWdodCcsIGNleD0wLjcsIHBjaD0xNSwgcHQuY2V4PTEsIGNvbD0nYmxhY2snLCBidHk9J24nLA0KICAgICAgIGxlZ2VuZCA9ICdGaWxsZWQgcG9pbnRzOiBkZWdyZWUgPiAwLjk1IHF1YW50aWxlJykNCiNsZWdlbmQ9J2RlZ3JlZSBvdmVyIDAuOTUgcXVhbnRpbGUgYXMgZmlsbGVkIHBvaW50cycpDQoNCnBhcihtYXIgPSBjKDUsIDQsIDQsIDIpICsgMC4xKQ0KYGBgDQoNCiMjIENlbnRyYWxpenphemlvbmUgZGkgdW4gZ3JhZm8NCg0KUGFzc2lhbW8gYWRlc3NvIGFnbGkgKippbmRpY2kgZGkgY2VudHJhbGl6emF6aW9uZSoqIGRlbGxhIHJldGU6IGVzc2kgdmFubm8gYSBtaXN1cmFyZSBpbCBncmFkbyBkaSBldGVyb2dlbmVpdMOgIGRlaSBub2RpIHJpc3BldHRvIGFsbGEgKmNlbnRyYWxpdMOgIGFuYWxpenphdGEqIChkZWdyZWUsIGNsb3NlbmVzcyBlIGJldHdlZW5uZXNzKToNClxiZWdpbntlcXVhdGlvbip9DQpDSSA9IFxmcmFje1xzdW1faSAoQ197XG1heH0tQ19pKX17XG1heF9ZIFxzdW1faSAoQ197XG1heH0tQ19pKX0gXGluIFswLDFdDQpcZW5ke2VxdWF0aW9uKn0NCg0KKiAqKiRDX2kkKiogw6ggaWwgdmFsb3JlIGRpICoqY2VudHJhbGl0w6Agc3UgY2lhc2N1biBub2RvICRpJCwqKiBlICRDX3tcbWF4fT1cbWF4X2kgQ19pJCwgDQoqIGNvbiAkXG1heF9ZJCByaWZlcml0byBzdSB0dXR0ZSBsZSBwb3NzaWJpbGkgY29uZmlndXJhemlvbmkgKiooJFxSaWdodGFycm93JCAgY29uZi4gYSBzdGVsbGEpKioNCg0KTmUgc2VndWUgY2hlDQoNCiogUGVyICoqJENJPTAkLCoqIHR1dHRpIGkgbm9kaSBzb25vICoqdWd1YWxtZW50ZSBjZW50cmFsaSoqICgqY29uZi4gYSBjZXJjaGlvKikNCiogUGVyICoqJENJPTEkLCoqIGMnw6ggdW4gKipzb2xvIG5vZG8gY2VudHJhbGUqKiBlIGdsaSBhbHRyaSBtaW5pbWFsbWVudGUgY2VudHJhbGkgKCpjb25mLiBhIHN0ZWxsYSopDQoNCmBgYHtyfQ0KQ0lkZWcgPSBjZW50cl9kZWdyZWUobWFydmVsX1VtYXhjb25uLCBsb29wcyA9IEYpJGNlbnRyYWxpemF0aW9uDQpDSWNsbyA9IGNlbnRyX2NsbyhtYXJ2ZWxfVW1heGNvbm4pJGNlbnRyYWxpemF0aW9uDQpDSWJldCA9IGNlbnRyX2JldHcobWFydmVsX1VtYXhjb25uLCBkaXJlY3RlZCA9IEYpJGNlbnRyYWxpemF0aW9uDQoNCihjKENJZGVnLCBDSWNsbywgQ0liZXQpKQ0KI3BhcihtYXI9Yyg1LDQsNCwyKSswLjEpICNtZnJvdyA9IGMoMSwxKSwgIA0KcGFyKG1mcm93ID0gYygxLDMpKQ0KcGxvdF9jZW50cmFsaXR5KGRlZ19ub3JtLCB0aHJfcSwgVCk7IHRpdGxlKHBhc3RlKCdkZWcuIGNlbnRyLiAnLCByb3VuZChDSWRlZywzKSkpDQpwbG90X2NlbnRyYWxpdHkoY2xvX25vcm0sIHRocl9xKTsgICAgdGl0bGUocGFzdGUoJ2Nsby4gY2VudHIuICcsIHJvdW5kKENJY2xvLDMpKSkNCnBsb3RfY2VudHJhbGl0eShiZXRfbm9ybSwgdGhyX3EpOyAgICB0aXRsZShwYXN0ZSgnYmV0LiBjZW50ci4gJywgcm91bmQoQ0liZXQsMykpKQ0KcGFyKG1mcm93ID0gYygxLDEpKQ0KDQpgYGANCiMjIyBDb25zaWRlcmF6aW9uaQ0KDQoqIEwnaW5kaWNlIGRpICoqY2VudHJhbGl6emF6aW9uZSBzdWxsYSBkZWdyZWUqKiBjZW50cmFsaXR5IG1pc3VyYSBxdWFudG8gbGUgY29ubmVzc2lvbmkgc2lhbm8gY29uY2VudHJhdGUgaW4gcG9jaGkgbm9kaSwgZWQgaWwgc3VvICoqYmFzc28gdmFsb3JlKiogaW5kaWNhIGNoZSBsZSAqKmNvbm5lc3Npb25pIHNvbm8gZGlzdHJpYnVpdGUgaW4gbW9kbyB1bmlmb3JtZSoqIHRyYSBpIG5vZGksIG9zc2lhIA0KICAgLSBsYSBtYWdnaW9yIHBhcnRlIGRlaSBub2RpIG5lbGxhIHJldGUgaGEgdW4gbnVtZXJvIHNpbWlsZSBkaSBjb25uZXNzaW9uaSBkaXJldHRlIChzZWJiZW5lIGFsY3VuaSBub2RpIGNvbWUgU3BpZGVyLU1hbiBlIENhcGl0YW4gQW1lcmljYSBhYmJpYW5vIHBpw7kgY29ubmVzc2lvbmkgZGkgYWx0cmksIGxhIGRpZmZlcmVuemEgbm9uIMOoIGNvc8OsIG1hcmNhdGEpDQogICAtIG5vbiBjaSBzb25vIHBvY2hpIG5vZGkgY2hlIGRvbWluYW5vIGxhIHJldGUgY29uIHVuIG51bWVybyBlY2Nlc3Npdm8gZGkgY29ubmVzc2lvbmkgKHRpcG8gY29uZi4gYSBjZXJjaGlvKS4NCiogTCdpbmRpY2UgZGkgKipjZW50cmFsaXp6YXppb25lIHN1bGxhIGNsb3NlbmVzcyoqIGNlbnRyYWxpdHkgaW5kaWNhIHF1YW50byBsYSAidmljaW5hbnphIiBzaWEgY29uY2VudHJhdGEgaW4gcG9jaGkgbm9kaSAoaW5kaWNlIGVsZXZhdG8pIG9wcHVyZSBsZSBkaXN0YW56ZSBtZWRpZSB0cmEgaSBub2RpIHNvbm8gcGnDuSB1bmlmb3JtaSAoaW5kaWNlIGJhc3NvKTsgaWwgKip2YWxvcmUgbWVkaW8tYmFzc28qKiBpbmRpY2EgY2hlICoqYWxjdW5pIG5vZGkgc29ubyBwacO5IGNlbnRyYWxpKiogZSBwb3Nzb25vIHJhZ2dpdW5nZXJlIGdsaSBhbHRyaSBwacO5IGZhY2lsbWVudGUsICoqbWEgbGEgbWFnZ2lvciBwYXJ0ZSBub24gw6ggZWNjZXNzaXZhbWVudGUgbG9udGFuYSoqDQoqIEwnaW5kaWNlIGRpICoqY2VudHJhbGl6emF6aW9uZSBzdWxsYSBiZXR3ZWVubmVzcyoqIGNlbnRyYWxpdHkgaW5kaWNhIHF1YW50byBpbCBjb250cm9sbG8gc3VpIHBlcmNvcnNpIHBpw7kgYnJldmkgc2lhIGNvbmNlbnRyYXRvIGluIHBvY2hpIG5vZGksIGVkIGlsIHN1byAqKnZhbG9yZSBlbGV2YXRvKiogc2lnbmlmaWNhIGNoZSAqKnBvY2hpICJub2RpIHBvbnRlIioqIGNvbnRyb2xsYW5vIHVuIGdyYW4gbnVtZXJvIGRpIHBlcmNvcnNpIHBpw7kgYnJldmksIGZ1bmdlbmRvIGRhICoqImNvbGxpIGRpIGJvdHRpZ2xpYSIqKiBuZWxsYSByZXRlIChxdWluZGkgbGEgcmltb3ppb25lIGRpIHVuIG5vZG8gY29uIHVuJ2FsdGEgYmV0d2Vlbm5lc3MgY2VudHJhbGl0eSBwdcOyIGludGVycm9tcGVyZSBzaWduaWZpY2F0aXZhbWVudGUgbGEgY29tdW5pY2F6aW9uZSBlIGwnaW50ZXJhemlvbmUgdHJhIGRpdmVyc2UgcGFydGkgZGVsbGEgcmV0ZSkNCg0KTGEgY2VudHJhbGl0w6Agc3VwZXJpb3JlIGRpICoqU3BpZGVyLU1hbiwgVmVub20gZSBSZWQgU2t1bGwqKiBpbiB0dXR0ZSBsZSB2YXJpYW50aSBjb25zaWRlcmF0ZSBzdWdnZXJpc2NlIGNoZSBsZSBsb3JvIGludGVyYXppb25pIGUgcmVsYXppb25pIHNpYW5vICoqY3J1Y2lhbGkgcGVyIGxvIHN2aWx1cHBvIGRlbGxlIHRyYW1lKiouDQoNCklub2x0cmUsIGNvbnNpZGVyYW5kbyBjaGUgKipkdWUgZGkgbG9ybyBzb25vICJjYXR0aXZpIioqLCBzaSBjYXBpc2NlIGNvbWUgKipsZSBsb3JvIHRyYW1lKiogc2lhbm8gZm9uZGFtZW50YWxpIGFsbCdpbnRlcm5vIGRlbGxhIHJldGUsIGluIHF1YW50byAqKmVsZW1lbnRpIGNoZSByb21wb25vIGdsaSBlcXVpbGlicmkuKioNCg0KDQoNCiMgU3RvY2hhc3RpYyBCbG9jayBNb2RlbA0KUHJlc3VwcG9uZW5kbyB1biBtb2RlbGxvIGEgYmxvY2NoaSBsYXRlbnRpICRCXzEgXGRvdHMgQl9RJCwgc2lhICRcYm9sZHN5bWJvbHtcdmFydGhldGF9ID0gW1xhbHBoYV8xIFxkb3RzIFxhbHBoYV9RLCBccGlfezExfSBcZG90cyBccGlfe1FRfV1eXHRvcCQgaWwgdmV0dG9yZSBkaSBwYXJhbWV0cmkgYXNzb2NpYXRvIGFsIG1vZGVsbG8sIGRvdmU6DQoNCiogJFxhbHBoYV9rJCByYXBwcmVzZW50YSBsYSAqcHJvYmFiaWxpdMOgIGEgcHJpb3JpKiBwZXIgaWwgYmxvY2NvICRCX2skDQoqICRccGlfe2lqfSQgaW5kaWNhIGxhIHByb2JhYmlsaXTDoCBkaSBhdmVyZSB1biBhcmNvIHRyYSB1biBub2RvIGFmZmVyZW50ZSBhbCBibG9jY28gJEJfaSQgZWQgdW4gbm9kbyBhcHBhcnRlbmVudGUgYWwgYmxvY2NvICRCX2okDQoNClNpIHZ1b2xlIHN0aW1hcmUgaSBwYXJhbWV0cmkgJFxib2xkc3ltYm9se1x2YXJ0aGV0YX0kIGEgbWFzc2ltYSB2ZXJvc2ltaWdsaWFuemEgZGF0YSB1bmEgcmVhbGl6emF6aW9uZSAkXG1hdGh0dHtVfSQgKGlsIGdyYWZvIGluIGVzYW1lKSBwZXIgbGEgbWF0cmljZSBkaSBhZGlhY2VuemEgJFxtYXRoYmZ7WX0kOg0KXGJlZ2lue2VxdWF0aW9uKn0NClxoYXR7XGJvbGRzeW1ib2x7XHZhcnRoZXRhfX0gPSBcYXJnIFxtYXhfXHZhcnRoZXRhIFxsb2cgXG1hdGhzZntQfShcbWF0aGJme1l9PVxtYXRodHR7VX0pID0gXGFyZyBcbWF4X1x2YXJ0aGV0YSBcbG9nIFxzdW1fe1xtYXRodHR7Wn19IFxtYXRoc2Z7UH0oXG1hdGhiZntZfT1cbWF0aHR0e1l9fFxtYXRoYmZ7Wn09XG1hdGh0dHtafTsgXGJvbGRzeW1ib2x7XHZhcnRoZXRhfSkgXG1hdGhzZntQfShcbWF0aGJme1p9PVxtYXRodHR7Wn07IFxib2xkc3ltYm9se1x2YXJ0aGV0YX0pDQpcZW5ke2VxdWF0aW9uKn0NCmRvdmUgJFxtYXRoYmZ7Wn0gPSBce3pfe2lxfVx9JCDDqCBsYSBtYXRyaWNlIGFsZWF0b3JpYSBkaSB0dXR0ZSBsZSB2YXJpYWJpbGkgbGF0ZW50aSBuZWwgbW9kZWxsbyAkel97aXF9ID0gXGJvbGRzeW1ib2x7MX0oXHRleHR7bm9kbyAkaSBcaW4kIGJsb2NjbyAkQl9xJH0pJC4NCg0KTGEgZGV0ZXJtaW5hemlvbmUgZGkgJFxoYXR7XGJvbGRzeW1ib2x7XHZhcnRoZXRhfX0kIGF2dmllbmUgdHJhbWl0ZSB1biBhbGdvcml0bW8gZGkgRXhwZWN0YXRpb24tTWF4aW1pemF0aW9uIFZhcmlhemlvbmFsZSBjaGUgbWluaXp6YSB1biBsb3dlciBib3VuZCBkZWxsYSBsb2ctbGlrZWxpaG9vZCBhdHRyYXZlcnNvIHVuYSBmb3JtdWxhemlvbmUgdHJhdHRhYmlsZSBkZWwgcHJvYmxlbWEuDQoNCg0KIyMgU0JTIHN1IGBtYXJ2ZWxfVW1heGNvbm5gDQpBIGNhdXNhIGRlbGxhICoqc3BhcnNpdMOgIGRlbCBncmFmbyoqIGluIHF1ZXN0aW9uZSwgU0JNIGZhIG1vbHRhIGZhdGljYSBhIHRyb3ZhcmUgZGVpIGJsb2NjaGkgbGF0ZW50aToNCg0KKiBhbmNoZSBhcHBsaWNhbmRvIFNCTSBhbCBzb3R0b2dyYWZvIGBtYXJ2ZWxfVW1heGNvbm5gIChsYSBjb21wb25lbnRlIG1hZ2dpb3JtZW50ZSBjb25uZXNzYSksIHNpIGhhbm5vIGRlaSByaXN1bHRhdGkgcG9jbyBzb2RkaXNmYWNlbnRpLCBjb21lIHNpIHB1w7IgdmVkZXJlIGRpIHNlZ3VpdG87DQoqICoqbmVsIGNhc28gZGVsIGdyYWZvIGNvbXBsZXRvIGBtYXJ2ZWxfVWdyYXBoYCBhZGRpcml0dHVyYSB2aWVuZSByZXN0aXR1aXRvIHVuIHNvbG8gdW5pY28gYmxvY2NvLioqDQoNCg0KDQpgYGB7cn0NCmxpYnJhcnkoc2JtKQ0KbGlicmFyeShsYXR0aWNlKQ0KbGlicmFyeShjb3dwbG90KQ0KDQpZY29ubiA9IGFzX2FkamFjZW5jeV9tYXRyaXgobWFydmVsX1VtYXhjb25uLCBzcGFyc2U9RikNCnNibUNvbm4gPSBlc3RpbWF0ZVNpbXBsZVNCTShZY29ubiwgJ2Jlcm5vdWxsaScsIGVzdGltT3B0aW9ucz1saXN0KHZlcmJvc2l0eT0wKSkNCg0KDQpwbG90MSA8LSBnZ2RyYXcoKSArIGRyYXdfcGxvdCh7IHBsb3RNeU1hdHJpeChZY29ubil9KQ0KcGxvdDIgPC0gZ2dkcmF3KCkgKyBkcmF3X3Bsb3QoeyBwbG90KHNibUNvbm4sdHlwZT0iZGF0YSIpfSkNCnBsb3QzIDwtIGdnZHJhdygpICsgZHJhd19wbG90KHsgcGxvdChzYm1Db25uLCJleHBlY3RlZCIpfSkNCg0KIyBDb21iaW5hIGkgZ3JhZmljaSBpbiB1bmEgZ3JpZ2xpYSAxeDMNCnBsb3RfZ3JpZChwbG90MSwgcGxvdDIsIHBsb3QzLCBucm93ID0gMSkNCmBgYA0KDQojIyBTQk0gc3UgYG1hcnZlbF9VbWF4Y29ubmAgZXN0ZXNvDQoNCiMjIyBFc3RlbnNpb25lIGRlZ2xpIGFyY2hpIG5lbCBncmFmbw0KUGVyIHBvdGVyIHZhbHV0YXJlIGxvIFNCTSBpbiB1biBjb250ZXN0byBwacO5IHJpY2NvIGRpIGNvbm5lc3Npb25pIHRyYSBpIG5vZGksIGBtYXJ2ZWxfVW1heGNvbm5gIMOoIHN0YXRvIGVzdGVzbyBhbmRhbmRvIGEgcmVjdXBlcmFyZSBwZXIgb2duaSBwZXJzb25hZ2dpbyByZWxhdGl2byBhaSBzdW9pICoqMTgxIG5vZGkqKiBpbiBxdWFsZSBmdW1ldHRvIGF2ZXNzZSBmYXR0byBsYSBzdWEgY29tcGFyc2E7IGFkIGVzZW1waW8NCg0KfCBQZXJzb25hZ2dpb3wxwrAgRnVtZXR0b3wNCnw6LS0tfDotLS18DQp8RG9jdG9yIERvb218RmFudGFzdGljIEZvdXIgICAgICB8DQp8SHVsayAgICAgICB8VGhlIEluY3JlZGlibGUgSHVsayB8DQp8SXJvbiBNYW4gICB8VGFsZXMgb2YgU3VzcGVuc2UgICB8DQp8IExva2kgICAgICB8Sm91cm5leSBpbnRvIE15c3Rlcnl8DQp8TWFnbmV0byAgICB8VGhlIFVuY2FubnkgWC1NZW4gICB8DQp8VGhpbmcgICAgICB8RmFudGFzdGljIEZvdXIgICAgICB8DQp8VGhvciAgICAgICB8Sm91cm5leSBpbnRvIE15c3Rlcnl8DQoNCg0KUXVlc3RlIGluZm9ybWF6aW9uaSBwb2kgc29ubyBzdGF0ZSBpbXBpZWdhdGUgcGVyIGNyZWFyZSBkZWkgKipudW92aSBhcmNoaSoqIHBlciBvZ25pIGNvcHBpYSBkaSAqKnBlcnNvbmFnZ2kgYXBwYXJzaSBuZWxsYSBzdGVzc2EgY29sbGFuYSoqLCBhIHRlc3RpbW9uaWFuemEgZGkgdW4gcHJvYmFiaWxlIGxlZ2FtZSBkaSAqKmludGVyYXppb25lIGRlYm9sZS4qKg0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHhsKQ0KbWFydmVsX2NvbWljcyA8LSByZWFkX2V4Y2VsKCJtYXJ2ZWxfY29taWNzLnhsc3giKQ0KcGlkIDwtIG1hcnZlbF9jb21pY3MkaWQNCmZvciAobiBpbiAxOmxlbmd0aChvcmlnX2NoYXIpKQ0Kew0KICAgcGlkW3BpZCA9PSBvcmlnX2NoYXJbbl1dID0gbmV3X2NoYXJbbl0NCn0NCnBpZCA9IGdzdWIoIlxccypcXChbXlxcKV0rXFwpIiwgIiIsIHBpZCkNCg0Kc2VxX3BpZCA9IG1hdGNoKFYobWFydmVsX1VtYXhjb25uKSRuYW1lLCBwaWQpDQpwaWQgPSBwaWRbc2VxX3BpZF0NCmNvbWljcyA9IGFzLmZhY3RvcihtYXJ2ZWxfY29taWNzJGNvbWljc1tzZXFfcGlkXSkNCg0Kc2NvcmVfY29taWNzIDwtIHRhYmxlKGNvbWljcykNCmNvbWljc19yYW5rIDwtIHNvcnQoc2NvcmVfY29taWNzLCBkZWNyZWFzaW5nPVQpDQpwYXIobWFyPWMoOSw0LDIsMCkpDQpiYXJwbG90KGNvbWljc19yYW5rLA0KICAgICAgICBtYWluID0gIkNvbWljcyBjaGFyYWN0ZXIgcmFua2luZyIsDQogICAgICAgIHhsYWIgPSAiIiwNCiAgICAgICAgeWxhYiA9ICIjIGNoYXJhY3RlcnMiLA0KICAgICAgICBjb2w9J3BhbGVncmVlbjMnLCBib3JkZXI9J3BhbGVncmVlbjQnLA0KICAgICAgICBsYXMgPSAyLCAjIEV0aWNoZXR0ZSBkZWxsJ2Fzc2UgeCB2ZXJ0aWNhbGkNCiAgICAgICAgY2V4Lm5hbWVzID0gMC41IA0KKQ0KZ3JpZChsdHk9J3NvbGlkJywgbHdkPTAuNSwgY29sPSd3aGl0ZScsIG54PU5BLCBueT1OVUxMKQ0KcGFyKG1hcj1jKDUsNCw0LDIpKzAuMSkNCmBgYA0KDQpgYGB7cn0NCiMgRXN0ZW5zaW9uZSBkZWwgZ3JhZm8gbWFydmVsX1VtYXhjb25uDQpWKG1hcnZlbF9VbWF4Y29ubikkY29taWNzID0gY29taWNzDQpZZXh0ID0gMiphc19hZGphY2VuY3lfbWF0cml4KG1hcnZlbF9VbWF4Y29ubiwgc3BhcnNlPUYpDQpmb3IgKGkgaW4gMTpsZW5ndGgoY29taWNzKSkNCnsNCiAgIHBpID0gcGlkW2ldDQogICBmb3IgKGogaW4gMTpsZW5ndGgoY29taWNzKSkgDQogICB7DQogICAgICBwaiA9IHBpZFtqXQ0KICAgICAgaWYgKGNvbWljc1tpXSA9PSBjb21pY3Nbal0gJiYgcGkgIT0gcGopIA0KICAgICAgew0KICAgICAgICAgI1lleHRbcGksIHBqXSA9ICAxDQogICAgICAgICAjWWV4dFtwaiwgcGldID0gIDENCiAgICAgICAgIA0KICAgICAgICAgaWYgKFlleHRbcGksIHBqXT09MCkNCiAgICAgICAgIHsNCiAgICAgICAgICAgIFlleHRbcGksIHBqXSA9IDENCiAgICAgICAgICAgIFlleHRbcGosIHBpXSA9IDENCiAgICAgICAgIH0NCiAgICAgIH0NCiAgIH0NCn0NCm1hcnZlbF9VZXh0IDwtIGdyYXBoX2Zyb21fYWRqYWNlbmN5X21hdHJpeChZZXh0LCBtb2RlID0gJ3VuZGlyZWN0ZWQnLCB3ZWlnaHRlZD1UKQ0KVihtYXJ2ZWxfVWV4dCkkY29taWNzID0gY29taWNzDQpWKG1hcnZlbF9VZXh0KSRzaXplID0gMy41DQpWKG1hcnZlbF9VZXh0KSRsYWJlbC5jZXggPSAwLjMNClYobWFydmVsX1VleHQpJGxhYmVsLmZhbWlseSA9ICdzYW5zJw0KVihtYXJ2ZWxfVWV4dCkkbGFiZWwuY29sb3I9J2JsYWNrJw0KRShtYXJ2ZWxfVWV4dCkkd2lkdGggPSAwLjYNCkUobWFydmVsX1VleHQpJGNvbG9yID0gImdyYXkiDQoNCg0KY29sb3JfY29taWNzIDwtIHJlcCgiZGFya2dyYXkiLCBsZW5ndGgoY29taWNzX3JhbmspKQ0KY29sb3JfY29taWNzWzE6Nl0gPSBjKCdsaWdodHNreWJsdWUnLCAnc2FuZHlicm93bicsICd5ZWxsb3dncmVlbicsICdwaW5rJywgJ2dvbGQzJywgJ3BsdW0zJykNCmNvbG9yX25vZGVCeUNvbWljcyA9IHJlcCgiZGFya2dyYXkiLCB2Y291bnQobWFydmVsX1VleHQpKQ0KZm9yIChpIGluIDE6dmNvdW50KG1hcnZlbF9VZXh0KSkgew0KICBjb20gPSBWKG1hcnZlbF9VZXh0KSRjb21pY3NbaV0NCiAgICBpZF9jb20gPSB3aGljaChuYW1lcyhjb21pY3NfcmFuaykgPT0gY29tKQ0KICAgIGNvbG9yX25vZGVCeUNvbWljc1tpXSA9IGNvbG9yX2NvbWljc1tpZF9jb21dDQp9DQpjb2xfbGVnID0gYyhjb2xvcl9jb21pY3NbMTo2XSwgJ2RhcmtncmF5JykNCmxhYl9sZWcgPSBjKG5hbWVzKGNvbWljc19yYW5rKVsxOjZdLCAnb3RoZXJzJykNCg0KcGFyKG1hciA9IGMoMCwgMCwgMCwgMCkpICNtZnJvdyA9IGMoMSwyKSwgDQpzZXQuc2VlZChteXNlZWQpDQpwbG90KG1hcnZlbF9VbWF4Y29ubiwNCiAgICAgbGF5b3V0PWxheW91dF9tYXhjb25uLA0KICAgICB2ZXJ0ZXguZnJhbWUud2lkdGggPSAwLjUsDQogICAgIHZlcnRleC5jb2xvcj0gY29sb3Jfbm9kZUJ5Q29taWNzLA0KICAgICBlZGdlLndpZHRoID0gMC4xLA0KKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sYWJfbGVnLCBmaWxsPWNvbF9sZWcsIHRpdGxlPSdDb21pY3MnLCBidHk9J24nLCBjZXg9MC42KQ0KcGFyKG1hcj1jKDUsNCw0LDIpKzAuMSkNCg0KY29sb3JfZWRnZSA9IHJlcCgibGlnaHRncmF5IiwgbGVuZ3RoKEUobWFydmVsX1VleHQpJHdlaWdodCkpDQppZHN0cm9uZyA9IHdoaWNoKEUobWFydmVsX1VleHQpJHdlaWdodD4xKQ0KY29sb3JfZWRnZVtpZHN0cm9uZ10gPSAnYmxhY2snDQoNCnBhcihtYXIgPSBjKDAsIDAsIDAsIDApKQ0Kc2V0LnNlZWQobXlzZWVkKQ0KcGxvdChtYXJ2ZWxfVWV4dCwNCiAgICAgbGF5b3V0PWxheW91dF9tYXhjb25uLA0KICAgICB2ZXJ0ZXguZnJhbWUud2lkdGggPSAwLjUsDQogICAgIHZlcnRleC5jb2xvcj0gY29sb3Jfbm9kZUJ5Q29taWNzLCNWKG1hcnZlbF9VZXh0KSRjb21pY3MsDQogICAgIGVkZ2Uud2lkdGggPSAwLjEqRShtYXJ2ZWxfVWV4dCkkd2VpZ2h0LA0KICAgICBlZGdlLmNvbG9yID0gY29sb3JfZWRnZSNFKG1hcnZlbF9VZXh0KSR3ZWlnaHQsDQopDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWxhYl9sZWcsIGZpbGw9Y29sX2xlZywgdGl0bGU9J0NvbWljcycsIGJ0eT0nbicsIGNleD0wLjYpDQpwYXIobWFyPWMoNSw0LDQsMikrMC4xKSAjbWZyb3cgPSBjKDEsMSksICANCmBgYA0KIyMjIEFwcGxpY2F6aW9uZSBkZWxsbyBTQk0NCg0KIyMjIyBWZXJzaW9uZSBiaW5hcml6emF0YSAoQmVybm91bGxpKQ0KDQohW1NlbGV6aW9uZSBkZWwgIyBRIGRpIGJsb2NjaGkgbGF0ZW50aSB0cmFtaXRlIElDTF0oSUNMYmVyLnBuZykNCg0KYGBge3IgcGxvdF9tYXRyaXhfZ3JpZCwgZmlnLnNob3c9J2hvbGQnfQ0KbGlicmFyeShjb2xvcnNwYWNlKQ0KDQpZZXh0X2JpbmFyeSA8LSBpZmVsc2UoWWV4dCA+IDAuNSwgMSwgMCkNCnNibSA9IGVzdGltYXRlU2ltcGxlU0JNKFlleHRfYmluYXJ5LCAnYmVybm91bGxpJywgZXN0aW1PcHRpb25zPWxpc3QodmVyYm9zaXR5PTApKQ0KDQpwbG90MSA8LSBnZ2RyYXcoKSArIGRyYXdfcGxvdCh7IHBsb3RNeU1hdHJpeChZZXh0X2JpbmFyeSkgfSkNCnBsb3QyIDwtIGdnZHJhdygpICsgZHJhd19wbG90KHsgcGxvdChzYm0sIHR5cGUgPSAiZGF0YSIpIH0pDQpwbG90MyA8LSBnZ2RyYXcoKSArIGRyYXdfcGxvdCh7IHBsb3Qoc2JtLCAiZXhwZWN0ZWQiKSB9KQ0KDQpwbG90X2dyaWQocGxvdDEsIHBsb3QyLCBwbG90MywgbnJvdyA9IDEpDQpgYGANCk5lbGxhIGZpZ3VyYSBzdXBlcmlvcmUsIGRhIHNpbmlzdHJhIHZlcnNvIGRlc3RyYTogbGEgbWF0cmljZSBkaSBhZGlhY2VuemEgYmluYXJpYSBpbiBpbmdyZXNzbywgbGEgbWF0cmljZSBvcmdhbml6emF0YSBpbiBibG9jY2hpIGUgbGEgc3VhIHZlcnNpb25lIGluIHRlcm1pbmkgZGkgcHJvYmFiaWxpdMOgIChjaGUgcml2ZWRyZW1vIGFuY2hlIGRvcG8pLg0KDQpgYGB7cn0NCihzYm0kYmxvY2tQcm9wKQ0KcGFyKG1hcj1jKDQsNCwzLDApKQ0KYmFycGxvdChzYm0kYmxvY2tQcm9wLA0KICAgICAgICBtYWluID0gInNibSRibG9ja1Byb3AiLA0KICAgICAgICB4bGFiID0gImJsb2NrcyIsDQogICAgICAgIHlsYWIgPSAicHJvYi4iLA0KICAgICAgICBjb2w9J3BhbGVncmVlbjMnLCBib3JkZXI9J3BhbGVncmVlbjQnLA0KICAgICAgICAjIGxhcyA9IDIsIA0KICAgICAgICBjZXgubmFtZXMgPSAxLA0KICAgICAgICBuYW1lcy5hcmcgPSAxOmxlbmd0aChzYm0kYmxvY2tQcm9wKQ0KKQ0KZ3JpZChsdHk9J3NvbGlkJywgbHdkPTAuNSwgY29sPSd3aGl0ZScsIG54PU5BLCBueT1OVUxMKQ0KcGFyKG1hcj1jKDUsNCw0LDIpKzAuMSkNCmBgYA0KU29wcmEsIGxlICoqcHJvYmFiaWxpdMOgIF9hIHByaW9yaV8gZGkgYXBwYXJ0ZW56YSBkZWkgdmFyaSBub2RpIGFpIGRpdmVyc2kgYmxvY2NoaS4qKg0KDQoNCmBgYHtyfQ0KIyBjb25uZWN0aXZpdHkgcGFyYW1ldGVycw0KKHJvdW5kKHNibSRjb25uZWN0UGFyYW0kbWVhbiwzKSkNCmxldmVscGxvdChzYm0kY29ubmVjdFBhcmFtJG1lYW4sDQogICAgICAgICAgY29sLnJlZ2lvbnMgPSBjb2xvclJhbXBQYWxldHRlKGMoInllbGxvdyIsICJwdXJwbGUiKSkoMTAwKSwgDQogICAgICAgICAgbWFpbiA9ICdzYm0kY29ubmVjdFBhcmFtJG1lYW4nLA0KICAgICAgICAgIHhsYWIgPSAiQ29sb25uZSIsIHlsYWIgPSAiUmlnaGUiKQ0KYGBgDQpTb3ByYSwgbGUgKnByb2JhYmlsaXTDoCBkaSBjb25uZXNzaW9uZSBpbnRyYS0gZSBpbnRlci0gYmxvY2NvKiAoY29uIGxlIHJpZ2hlIG9yZGluYXRlIGRhbCBiYXNzbyB2ZXJzbyBsJ2FsdG8pOyBpbiBxdWVzdG8gY2Fzbywgc2ViYmVuZSBsbyBTQk0gbm9uIGNlcmNoaSBkaSBwcm9wb3NpdG8gbGUgY29tdW5pdMOgLCBzb25vIHN0YXRpIHJpdHJvdmF0aSA4IGNsdXN0ZXIgY29uIGFsdGEgcHJvYmFiaWxpdMOgIGludGVybmEsIG1lbnRyZSBpIGNvbGxlZ2FtZW50aSB0cmEgYmxvY2NoaSBkaXZlcnNpIHNvbm8gbW9sdG8gcG9jbyBwcm9iYWJpbGkuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQobXlzZWVkKQ0KcGFyKG1hcj1jKDAsMCwwLDApKQ0KcGxvdChzYm0sIHR5cGUgPSAibWVzbyIpDQpwYXIobWFyPWMoNSw0LDQsMikrMC4xKQ0KYGBgDQpTb3ByYSwgbGEgcmFwcHJlc2VudGF6aW9uZSBpbiBmb3JtYSAqbWVzb3Njb3BpY2EqIGRlbGxhIG1hdHJpY2UgcHJlY2VkZW50ZToNCg0KKiBvZ25pIG5vZG8gaW5kaWNhIHVuIGJsb2NjbywgZGkgZGltZW5zaW9uaSBwcm9wb3J6aW9uYWxpIGFsICMgZGkgbm9kaSBhbGxvY2F0aSBpbiBvZ251bm8gZGkgZXNzaQ0KKiBsbyBzcGVzc29yZSBkZWdsaSBhcmNoaSDDqCBwcm9wb3J6aW9uYWxlIGFsbGUgcHJvYmFiaWxpdMOgIGRpIGNyZWFyZSBhcmNoaSB0cmEgbm9kaSBkaSBibG9jY2hpIGRpdmVyc2kgKG8gZGVsbG8gc3Rlc3NvIGJsb2NjbywgcGVyIGdsaSBhcmNoaSAqc2VsZiBsb29wKikNCg0KDQojIyMjIEFzc2VnbmFtZW50byBkZWkgbm9kaSBwZXIgYmxvY2NvDQpgYGB7cn0NCmNvbG9yX2Jsb2NrcyA9IHJlcCgiZGFya2dyYXkiLCBsZW5ndGgoc2JtJG1lbWJlcnNoaXBzKSkNCmNvbG9yX2Jsb2Nrc1sxOjhdID0gYygnZG9kZ2VyYmx1ZTInLCAnb3JhbmdlJywgJ2xpbWVncmVlbicsICdwaW5rJywgJ2dvbGQ0JywgJ21lZGl1bW9yY2hpZCcsICdnb2xkJywgJ2ZpcmVicmljaycpDQoNCmJsb2NrX3JhbmsgPSBzb3J0KHNibSRtZW1iZXJzaGlwcywgZGVjcmVhc2luZz1UKQ0KY29sb3Jfbm9kZUJ5QmxvY2tzID0gcmVwKCJkYXJrZ3JheSIsIHZjb3VudChtYXJ2ZWxfVWV4dCkpDQpmb3IgKGkgaW4gMTp2Y291bnQobWFydmVsX1VleHQpKSANCnsNCiAgIGlkX2NvbSA9IHNibSRtZW1iZXJzaGlwc1tpXQ0KICAgY29sb3Jfbm9kZUJ5QmxvY2tzW2ldID0gY29sb3JfYmxvY2tzW2lkX2NvbV0NCn0NCmNvbF9sZWcgPSBjKGNvbG9yX2Jsb2Nrc1sxOjldKQ0KbGFiX2xlZyA9IGMoMTo5KQ0KI2xhYl9sZWcgPC0gYyhuYW1lcyhjb2xvcl9ibG9ja3MpWzE6OF0sICc+OCcpDQoNCnBhcihtYXI9YygwLDAsMCwwKSkNCnBsb3QobWFydmVsX1VleHQsDQogICAgIGxheW91dD1sYXlvdXRfbWF4Y29ubiwNCiAgICAgdmVydGV4LmZyYW1lLndpZHRoID0gMC41LA0KICAgICB2ZXJ0ZXguY29sb3I9IGNvbG9yX25vZGVCeUJsb2Nrcywjc2JtJG1lbWJlcnNoaXBzLA0KICAgICBlZGdlLndpZHRoID0gMC4xLA0KKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sYWJfbGVnLCBmaWxsPWNvbF9sZWcsIHRpdGxlPSdCbG9ja3MnLCBidHk9J24nLCBjZXg9MC42KQ0KcGFyKG1hcj1jKDUsNCw0LDIpKzAuMSkNCmBgYA0KIVtDb25mcm9udG8gdHJhIGNvbWljcyBlIGJsb2NjaGldKE5vZGVCeUdyb3Vwc0NtcC5wbmcpDQoNCkRpIGZhdHRvIFNCTSBoYSByaXRyb3ZhdG8gdW4gcmFnZ3J1cHBhbWVudG8gaW4gYmFzZSBhbCBmdW1ldHRvIGRpIHByaW1hIGNvbXBhcnNhOg0KDQoqIGRhbCBjb25mcm9udG8gdmlzaXZvIHNpIG5vdGEgY2hlIGkgcHJpbWkgMyBibG9jY2hpIGNvcnJpc3BvbmRvbm8gYWkgcHJpbWkgdHJlIGZ1bWV0dGksIGUgYW5jaGUgcGVyIGlsIHNlc3RvOw0KKiBpbCBibG9jY28gNSBkb3ZyZWJiZSBjb3JyaXNwb25kZXJlIGFsIGZ1bWV0dG8gIzQgZSB2aWNldmVyc2EgKGkgZnVtZXR0aSAjMyw0LDUgYXZldmFubyBsYSBzdGVzc2EgbnVtZXJvc2l0w6AgZGkgbm9kaSkNCiogaWwgNyBibG9jY28gY29ycmlzcG9uZGUgYWwgZnVtZXR0byAjNyBwZXIgbnVtZXJvc2l0w6AgKFRhbGVzIHRvIEFzdG9uaXNoLCBub24gaW5kaWNhdG8gY29tZSBjb2RpY2UgY29sb3JlKQ0KKiBwZXIgaWwgZ3J1cHBvIDgsIHNpIHZlZGEgbGEgc2VndWVudGUgdGFiZWxsYQ0KDQpgYGB7cn0NCmlkNyA9IHdoaWNoKHNibSRtZW1iZXJzaGlwcz09NykNCnRhYjcgPSBkYXRhLmZyYW1lKFBlcnNvbmFnZ2lvID0gVihtYXJ2ZWxfVWV4dCkkbmFtZVtpZDddLCBGdW1ldHRvID0gVihtYXJ2ZWxfVWV4dCkkY29taWNzW2lkN10pDQoodGFiNykNCg0KaWQ4ID0gd2hpY2goc2JtJG1lbWJlcnNoaXBzPT04KQ0KdGFiOCA9IGRhdGEuZnJhbWUoUGVyc29uYWdnaW8gPSBWKG1hcnZlbF9VZXh0KSRuYW1lW2lkOF0sIEZ1bWV0dG8gPSBWKG1hcnZlbF9VZXh0KSRjb21pY3NbaWQ4XSkNCih0YWI4KQ0KYGBgDQoNCg0KIyMjIyBQcm9iYWJpbGl0w6AgYSBwb3N0ZXJpb3JpIGRpIGFzc2VnbmF6aW9uZSBkaSB1biBub2RvIGFpIHZhcmkgYmxvY2NoaQ0KYGBge3J9DQpsZXZlbHBsb3QodChzYm0kcHJvYk1lbWJlcnNoaXBzKSwNCiAgICAgICAgICBjb2wucmVnaW9ucyA9IGNvbG9yUmFtcFBhbGV0dGUoYygieWVsbG93IiwgInB1cnBsZSIpKSgxMDApLCANCiAgICAgICAgICBtYWluID0gJ3NibSRwcm9iTWVtYmVyc2hpcHMnLA0KICAgICAgICAgIHhsYWIgPSAiYmxvY2NoaSIsIHlsYWIgPSAibm9kaSIpDQpgYGANCg0KIyMjIyBWZXJzaW9uZSBwZXNhdGEgKE11bHRpbm9taWFsZT8/PykNCmBgYHtyfQ0KIyBzYm0gPSBlc3RpbWF0ZVNpbXBsZVNCTShZZXh0LCAncG9pc3NvbicsIGVzdGltT3B0aW9ucz1saXN0KHZlcmJvc2l0eT0wKSkNCiMgDQojIHBsb3QxIDwtIGdnZHJhdygpICsgZHJhd19wbG90KHsgcGxvdE15TWF0cml4KFlleHQpIH0pDQojIHBsb3QyIDwtIGdnZHJhdygpICsgZHJhd19wbG90KHsgcGxvdChzYm0sIHR5cGUgPSAiZGF0YSIpIH0pDQojIHBsb3QzIDwtIGdnZHJhdygpICsgZHJhd19wbG90KHsgcGxvdChzYm0sICJleHBlY3RlZCIpIH0pDQojIA0KIyBwbG90X2dyaWQocGxvdDEsIHBsb3QyLCBwbG90MywgbnJvdyA9IDEpDQojIA0KIyBibG9ja19yYW5rID0gc29ydChzYm0kbWVtYmVyc2hpcHMsIGRlY3JlYXNpbmc9VCkNCiMgY29sb3Jfbm9kZUJ5QmxvY2tzID0gcmVwKCJkYXJrZ3JheSIsIHZjb3VudChtYXJ2ZWxfVWV4dCkpDQojIGZvciAoaSBpbiAxOnZjb3VudChtYXJ2ZWxfVWV4dCkpIA0KIyB7DQojICAgIGJsayA9IHNibSRtZW1iZXJzaGlwc1tpXQ0KIyAgICBpZF9jb20gPC0gd2hpY2goYmxvY2tfcmFuayA9PSBibGspDQojICAgIGNvbG9yX25vZGVCeUJsb2Nrc1tpXSA9IGNvbG9yX2Jsb2Nrc1tpZF9jb21bMV1dDQojIH0NCiMgY29sX2xlZyA9IGMoY29sb3JfYmxvY2tzWzE6OF0sICdkYXJrZ3JheScpDQojIGxhYl9sZWcgPC0gYyhuYW1lcyhjb2xvcl9ibG9ja3MpWzE6OF0sICc+OCcpDQojIA0KIyBwYXIobWFyPWMoMCwwLDAsMCkpDQojIHBsb3QobWFydmVsX1VleHQsDQojICAgICAgbGF5b3V0PWxheW91dF9tYXhjb25uLA0KIyAgICAgIHZlcnRleC5mcmFtZS53aWR0aCA9IDAuNSwNCiMgICAgICB2ZXJ0ZXguY29sb3I9IHNibSRtZW1iZXJzaGlwcywNCiMgICAgICBlZGdlLndpZHRoID0gMC4xLA0KIyAgICAgIGVkZ2UuY29sb3IgPSBjb2xvcl9lZGdlLA0KIyApDQojIHBhcihtYXI9Yyg1LDQsNCwyKSswLjEpDQojIA0KIyAoc2JtJGJsb2NrUHJvcCkNCiMgcGFyKG1hcj1jKDQsNCwzLDApKQ0KIyBiYXJwbG90KHNibSRibG9ja1Byb3AsDQojICAgICAgICAgbWFpbiA9ICJzYm0kYmxvY2tQcm9wIiwNCiMgICAgICAgICB4bGFiID0gImJsb2NrcyIsDQojICAgICAgICAgeWxhYiA9ICJwcm9iLiIsDQojICAgICAgICAgY29sPSdwYWxlZ3JlZW4zJywgYm9yZGVyPSdwYWxlZ3JlZW40JywNCiMgICAgICAgICAjIGxhcyA9IDIsIA0KIyAgICAgICAgIGNleC5uYW1lcyA9IDEsDQojICAgICAgICAgbmFtZXMuYXJnID0gMTpsZW5ndGgoc2JtJGJsb2NrUHJvcCkNCiMgKQ0KIyBncmlkKGx0eT0nc29saWQnLCBsd2Q9MC41LCBjb2w9J3doaXRlJywgbng9TkEsIG55PU5VTEwpDQojIHBhcihtYXI9Yyg1LDQsNCwyKSswLjEpDQojIA0KIyAjIGNvbm5lY3Rpdml0eSBwYXJhbWV0ZXJzDQojIChyb3VuZChzYm0kY29ubmVjdFBhcmFtJG1lYW4sMykpDQojIGxldmVscGxvdChzYm0kY29ubmVjdFBhcmFtJG1lYW4sDQojICAgICAgICAgICBjb2wucmVnaW9ucyA9IGNvbG9yUmFtcFBhbGV0dGUoYygieWVsbG93IiwgInB1cnBsZSIpKSgxMDApLCANCiMgICAgICAgICAgIG1haW4gPSAnc2JtJGNvbm5lY3RQYXJhbSRtZWFuJywNCiMgICAgICAgICAgIHhsYWIgPSAiQ29sb25uZSIsIHlsYWIgPSAiUmlnaGUiKQ0KIyANCiMgc2V0LnNlZWQobXlzZWVkKQ0KIyBwYXIobWFyPWMoMCwwLDAsMCkpDQojIHBsb3Qoc2JtLCB0eXBlID0gIm1lc28iKQ0KIyBwYXIobWFyPWMoNSw0LDQsMikrMC4xKQ0KYGBgDQoNCg0K